Подтвердить что ты не робот

Самый чистый способ создания условного кода в шаблоне С++

Я пытаюсь запустить следующий код на С++:

#include <cmath>

template<typename T, bool> class ConditionalData {
};

template <typename T> class ConditionalData<T, false> {
};

template <typename T> class ConditionalData<T, true> {
private:
    T data;
public:
    void setData(T _data) { data = _data; }
};


template<bool hasdata> class A {
public:
    A() {
        ConditionalData<double,hasdata> data;
        if (hasdata) {
            data.setData(sin(cos(123.4)));
        }
    }
};


int main(int argNum, const char**argData) {
    A<false> test1;
    A<true> test2;
    return 0;
}

По сути, я хочу реализовать шаблонный класс A, в котором выполняются определенные операции в зависимости от параметра шаблона. Этим операциям нужны локальные переменные, которые я хочу выделить только при необходимости. Проблема, с которой я здесь сталкиваюсь, состоит в том, что тело

if (hasdata) {
    data.setData(3);
}
Условие

также создается для hasdata = false, которое не компилируется (с использованием g++ 5.2). Любые идеи, как это сделать самым чистым способом, не разбивая тело A:: A() на куски?

Исходный код, приведенный выше, является минимальным нерабочим примером. Реальная реализация для A:: A() относительно длинная, причем части, зависящие от "hasdata", распределяются равномерно по коду. Кроме того, "typename T", для которого будет использоваться класс A, является относительно сложным классом с тяжелыми конструкторами/деструкторами, поэтому я хочу, чтобы экземпляры T были выделены только при hasdata = true. Наконец, в вызовах data.setData(...) могут быть сложные вычисления в "...", которые должны выполняться только при необходимости.

4b9b3361

Ответ 1

Если вы можете позволить себе С++ 14, вы можете выразить условные ветки как общие лямбды. Преимущество в том, что они захватывают окружающие переменные, и решение не требует дополнительных функций-членов.

template <bool> struct tag {};

template <typename T, typename F>
auto static_if(tag<true>, T t, F f) { return t; }

template <typename T, typename F>
auto static_if(tag<false>, T t, F f) { return f; }

template <bool B, typename T, typename F>
auto static_if(T t, F f) { return static_if(tag<B>{}, t, f); }

template <bool B, typename T>
auto static_if(T t) { return static_if(tag<B>{}, t, [](auto&&...){}); }

// ...

ConditionalData<int, hasdata> data;        
static_if<hasdata>([&](auto& d)
{
    d.setData(3);
})(data);

DEMO

Ответ 2

Это общий шаблон, поэтому на самом деле есть статья, чтобы добавить constexpr_if в С++. Если это приведет к будущим версиям, это позволит вам сохранить свой код в значительной степени как есть.

template<bool hasdata> class A {
public:
    A() {
        ConditionalData<int,hasdata> data;
        constexpr_if (hasdata) {
        //^^^^^^^^^^ instead of plain if
            data.setData(3);
        }
    }
};

Теперь вам нужно будет сделать один из других ответов.

Ответ 3

Прежде всего, вам не нужны 3 версии class ConditionalData, потому что bool может быть либо true, либо false. Поэтому позвольте мне упростить его следующим образом:

template<typename T, bool = false> class ConditionalData {
};                 //^^^^^^^^^^^^

template <typename T> class ConditionalData<T, true> {
private:
    T data;
public:
    void setData(T _data) { data = _data; }
};

Во-вторых, чтобы ответить на ваш вопрос: кто бы ни был членом категории false, просто перегрузите их вне тела class следующим образом:

template<bool hasdata> class A { 
public:
    A() {
        ConditionalData<int,hasdata> data;
        if (hasdata) {
            data.setData(3);
        }
    }   
};

template<> A<false>::A() {}  // Does nothing for `false` condition

Ответ 4

вы можете определить setData для обеих ветвей, пустой для условия false:

template<typename T, bool> class ConditionalData {
};

template <typename T> class ConditionalData<T, false> {
    void setData(T _data) {}
};

template <typename T> class ConditionalData<T, true> {
private:
    T data;
public:
    void setData(T _data) { data = _data; }
};

template<bool hasdata> class A {
public:
    A() {
        ConditionalData<int,hasdata> data;
        data.setData(3);
    }
};

Ответ 5

Если вы не можете (/не хотите) изменить ConditionalData, вместо этого вы можете создать 2 метода:

template<typename T>
void SetData(ConditionalData<T, false>& , const T& ) {/* Nothing */}

template<typename T>
void SetData(ConditionalData<T, true>& c, const T& value) { c.setData(value); }

а затем

A() {
    ConditionalData<int, hasdata> data;
    SetData(data, 3);
}

Для более сложных случаев

template<typename T>
void A_impl_part1(ConditionalData<T, false>&) {/* Nothing */}

template<typename T>
void A_impl_part1(ConditionalData<T, true>& c) { c.setData(3); }

а затем

A() {
    ConditionalData<int, hasdata> data;
    A_impl_part1(data);
    // common part
    // A_impl_part2(data); // and so on
}

Ответ 6

Вы можете использовать препроцессор для генерации каждой вариации вашего класса в качестве специализированных шаблонов.

Во-первых, заголовок "template" мы будем генерировать специализации из:

ATemplate.h
//no include guards to allow multiple inclusion
template<>
class A<A_HAS_DATA>
{
public:
    A()
    {
#if A_HAS_DATA
        double data;
        if (hasdata) {
            data = sin(cos(123.4));
        }
#endif
    }
}

Затем мы фактически генерируем каждую специализацию для получения нормального заголовка для использования в вашем коде:

A.h
#pragma once

template<bool hasdata>
class A;

//Generate specialization for hasdata = true
#define A_HAS_DATA 1
#include "ATemplate.h"
#undef A_HAS_DATA
//(undef avoids redefinition warning)
//Generate specialization for hasdata = false
#define A_HAS_DATA 0
#include "ATemplate.h"
#undef A_HAS_DATA

По существу, вместо того, чтобы вручную писать каждую специализацию для каждого возможного случая (учитывая, что у вас может быть несколько таких параметров для включения/исключения данных), мы используем препроцессор для генерации каждого варианта, включая заголовок несколько раз, каждый раз с другим значение для препроцессора определяет (и) для получения другого результата.

Предпочитают использовать обычные шаблонные подходы, когда они работают, но если количество ручного дублирования кода (для определения всех возможных вариантов) становится слишком высоким, этот подход может работать (так как вы можете указать все "встроенные", а какие статические, если если бы у нас был один)