Настройка управляемых данными флагов в конструкторе производного класса

Скажем, у меня есть базовый класс с флагом внутри него, который должны установить производные классы:

struct Base
{
bool flag;
Base(bool flag):flag(flag) {}
};

Я хочу настроить, какие производные классы установить флаг true/false управляемым данными способом, то есть я хотел бы настроить это из заголовка.

struct Derived1 : Base
{
Derived1() : Base( expr ) {}
};

куда expr является что-то (еще не знаю, что), который может получить информацию из заголовка — скажите, Derived1 должен сделать flag правда или ложь. В идеале я получаю сообщение об ошибке, если создаю новый производный класс, но не могу указать флаг в заголовке, но это не обязательно. Таким образом, я могу просто изменить одно центральное место, чтобы внести изменения.

Какой идиоматический подход к этому?

1

Решение

Альтернативная версия, которая использует одну функцию, может быть более компактной:

struct Derived1 : Base
{
Derived1() : Base(theFlag(this)) {}
};

Тогда в шапке:

template <typename T>
bool theFlag(T*)
{
if (typeid(T) == typeid(Derived1)) return true;
if (typeid(T) == typeid(Derived2)) return false;
if (typeid(T) == typeid(Derived3)) return true;

throw std::runtime_error("No theFlag is given for this type");
}

Если вы женаты на проверке во время компиляции, лучшее, что вы можете сделать, это ввести немного дублирования:

template <typename T>
bool theFlag(T*)
{
static_assert(
std::is_same<T, Derived1>::value ||
std::is_same<T, Derived2>::value ||
std::is_same<T, Derived3>::value,
"No theFlag is given for this type");

if (typeid(T) == typeid(Derived1)) return true;
if (typeid(T) == typeid(Derived2)) return false;
if (typeid(T) == typeid(Derived3)) return true;
}

В основном это зависит от SFINAE — компилятор не сможет найти перегрузку для theFlag если вы назвали это с неподдерживаемым аргументом, по сути.

2

Другие решения

Вы могли бы написать:

struct Derived1 : Base
{
Derived1() : Base(Concept<Derived1>::theFlag) {}
};

И ваш заголовок может иметь следующее:

template <typename T>
struct Concept
{};

template <>
struct Concept<Derived1>
{
static const bool theFlag = true;
};

со специализацией, повторяемой для каждого типа.

Это то, что вы имели в виду? Компиляция не удастся, если вы не указали значение флага для некоторых DerivedN,

2

Я бы написал класс признаков для флагов и использовал бы макрос для определения специализаций:

#include <type_traits>

template<typename T>
struct FlagTrait {
static_assert(std::is_void<T>::value, "No flag defined for this type.");
};

#define DEFINE_FLAG(Type, Val)               \
template<>                           \
struct FlagTrait<class Type>         \
: std::integral_constant<bool, Val> {};

template<typename T>
constexpr bool getFlag(T) { return FlagTrait<T>::value; }

#define GET_FLAG getFlag(*this)

Теперь все, что нам нужно сделать для каждого нового производного класса, это добавить строку с именем класса и значением флага:

DEFINE_FLAG(Derived1, true)
DEFINE_FLAG(Derived2, false)

Использование:

struct Base
{
bool flag;
Base(bool flag):flag(flag) {}
};

struct Derived1 : Base
{
Derived1() : Base(GET_FLAG) {}
};

struct Derived2 : Base
{
Derived2() : Base(GET_FLAG) {}
};
2

Вот чистое решение во время компиляции:

struct Derived1 ;
struct Derived2 ;
template <typename Derived> struct Bootstrap
{
bool init(Derived1 *) { return true ; }
bool init(Derived2 *) { return false ; }
Bootstrap():flag(init(static_cast<Derived *>(this))){}
bool flag ;
};
struct Derived1: public Bootstrap <Derived1> {};
struct Derived2: public Bootstrap <Derived2> {};
int main()
{
Derived1 d1 ;
Derived2 d2 ;
std::cout<<d1.flag<<" "<<d2.flag<<std::endl ;
return 0 ;
}

РЕДАКТИРОВАТЬ

Как указано в «Гонках легкости на орбите», static_cast во время процесса ctor может вызывать неопределенное поведение (UB). Вот обновленная реализация, которая устраняет необходимость в операторе static_cast:

#include <iostream>
struct Derived1 ;
struct Derived2 ;
namespace BootstrapDetail
{
template <typename Identifier> bool init();
template <> bool init <Derived1>() { return true ; }
template <> bool init <Derived2>() { return false ; }
}
template <typename Derived> struct Bootstrap
{
Bootstrap(): flag(BootstrapDetail::init<Derived>()) {}
bool flag ;
};
struct Derived1: public Bootstrap <Derived1> {};
struct Derived2: public Bootstrap <Derived2> {};
int main()
{
Derived1 d1 ;
Derived2 d2 ;
std::cout<<d1.flag<<" "<<d2.flag<<std::endl ;
return 0 ;
}
1