Скажем, у меня есть базовый класс с флагом внутри него, который должны установить производные классы:
struct Base
{
bool flag;
Base(bool flag):flag(flag) {}
};
Я хочу настроить, какие производные классы установить флаг true
/false
управляемым данными способом, то есть я хотел бы настроить это из заголовка.
struct Derived1 : Base
{
Derived1() : Base( expr ) {}
};
куда expr
является что-то (еще не знаю, что), который может получить информацию из заголовка — скажите, Derived1
должен сделать flag
правда или ложь. В идеале я получаю сообщение об ошибке, если создаю новый производный класс, но не могу указать флаг в заголовке, но это не обязательно. Таким образом, я могу просто изменить одно центральное место, чтобы внести изменения.
Какой идиоматический подход к этому?
Альтернативная версия, которая использует одну функцию, может быть более компактной:
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
если вы назвали это с неподдерживаемым аргументом, по сути.
Вы могли бы написать:
struct Derived1 : Base
{
Derived1() : Base(Concept<Derived1>::theFlag) {}
};
И ваш заголовок может иметь следующее:
template <typename T>
struct Concept
{};
template <>
struct Concept<Derived1>
{
static const bool theFlag = true;
};
со специализацией, повторяемой для каждого типа.
Это то, что вы имели в виду? Компиляция не удастся, если вы не указали значение флага для некоторых DerivedN
,
Я бы написал класс признаков для флагов и использовал бы макрос для определения специализаций:
#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) {}
};
Вот чистое решение во время компиляции:
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 ;
}