问题
Say I have a base class with a flag inside of it which derived classes have to set:
struct Base
{
bool flag;
Base(bool flag):flag(flag) {}
};
I want to configure which derived classes set the flag to true
/false
in a data-driven way - i.e. I'd like to configure this from a header.
struct Derived1 : Base
{
Derived1() : Base( expr ) {}
};
Where expr
is something (don't know what yet) that is able to get the info from the header - tell whether Derived1
should make flag
true or false. Ideally, I'd get an error if I make a new derived class but fail to specify the flag in the header, but this isn't mandatory. This way I can just modify a single central location to make changes.
What's the idiomatic approach for this?
回答1:
An alternative version that uses a single function might be more compact:
struct Derived1 : Base
{
Derived1() : Base(theFlag(this)) {}
};
Then in the header:
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");
}
If you are married to the compile-time check, the best you could do is to introduce a bit of duplication:
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;
}
This basically relies on SFINAE - the compiler would not be able to find an overload for theFlag
if you called it with an unsupported argument, essentially.
回答2:
You could write:
struct Derived1 : Base
{
Derived1() : Base(Concept<Derived1>::theFlag) {}
};
And your header could have the following:
template <typename T>
struct Concept
{};
template <>
struct Concept<Derived1>
{
static const bool theFlag = true;
};
with the specialisation repeated for each type.
Is that what you meant? Compilation will fail when you didn't give a flag value for some DerivedN
.
回答3:
I'd write a trait class for flags and use a macro to define specializations:
#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)
Now all we need to do for each new derived class is to add a line with class name and value of the flag:
DEFINE_FLAG(Derived1, true)
DEFINE_FLAG(Derived2, false)
Usage:
struct Base
{
bool flag;
Base(bool flag):flag(flag) {}
};
struct Derived1 : Base
{
Derived1() : Base(GET_FLAG) {}
};
struct Derived2 : Base
{
Derived2() : Base(GET_FLAG) {}
};
回答4:
Here's a pure compile-time solution:
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 ;
}
EDIT
As pointed out by 'Lightness Races in Orbit', static_cast during ctor process can cause Undefined Behabiour (UB). Here's an updated implementation which negates the need for the static_cast operator:
#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 ;
}
来源:https://stackoverflow.com/questions/14686520/data-driven-flag-setting-in-derived-class-constructor