Data-driven flag setting in derived class constructor

大兔子大兔子 提交于 2019-12-11 07:27:00

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!