c++variadic-templatesstatic-methodsconstexprtag-dispatching

Variadic templates for multiple heritage assertions - "...redeclared with 3 template parameters..."


I am trying to implement my own std::is_base_of for my AVR programming (avr-gcc does not yet support <type_traits>. I took inspiration of the possible implementation on the cppreference page, it worked for a single type check. However, what I want to achieve is a statically executed validity checks of multiple types' heritage of one base class.

For simplicity I am using std::is_base_of for the actual check below, however my actual solution is close to what is in the cppreference page linked above.

I will use it for tag dispatching, more specifically to allow option tags in any order.


Option Tags

struct tOption {};
struct tThis : public tOption {};
struct tThat : public tOption {};
struct tElse {}; // Wrongly defined option tag!

Single Heritage Validator Struct

template<typename TBase, typename TCandidate>
struct isBaseOf {
    isBaseOf() = delete;
    static const bool value = std::is_base_of<TBase, TCandidate>::value;
};

static_assert(isBaseOf<tOption, tThat>::value, "Invalid option tag!"); // OK!
static_assert(isBaseOf<tOption, tElse>::value, "Invalid option tag!"); // ERROR! Invalid option tag!

Attempt on Multiple Checks (addition to the isBaseOf declaration above)

template<typename TBase, typename TCandidate, typename... TRest>
struct isBaseOf {
    isBaseOf() = delete;
    static const bool value = isBaseOf<TBase, TRest...>::value && 
                              std::is_base_of<TBase, TCandidate>::value;
};

This does not work. From what I see, I am not able to redeclare a template using a different number of types. However, I need at least two types in the last template construct. I tried with TBase as only parameter and just set the value to true, but the same issue is still here: error: redeclared with 3 template parameters


Usage

As mentioned, this is limited to a single check. Since my class (not shown here) uses variadic templates for any number of option tags (and avr-gcc does not support full c++14 with for-loops in constexpr functions), I want to be able to use parameter unpacking and still check that all option tags have heritage of my base tag (tOption).

template<typename... TOptions>
class tMyClass {
    static_assert(isBaseOf<tOption, TOptions...>::value, "Invalid option tag(s)!"); // <--- THIS
    // ...
};

Using Functions - Ugly and unwanted

I got it to work using a function instead of another struct, but I think this is confusing. I'd rather have one way of solving the issue throughout the recursive (static) stack. Also, this forces me to construct each tag, which is not very neat IMO.

template<typename TBase, typename TCandidate>
constexpr bool isBaseOf2(const TBase&, const TCandidate&) {
    return std::is_base_of<TBase, TCandidate>::value;
}

template<typename TBase, typename TCandidate, typename... TRest>
constexpr bool isBaseOf2(const TBase& base, const TCandidate&, const TRest&... rest) {
    return isBaseOf2(base, rest...) && std::is_base_of<TBase, TCandidate>::value;
}

static_assert(isBaseOf2(tOption{}, tThis{}, tThat{}), "Invalid option tag(s)!"); // OK!
static_assert(isBaseOf2(tOption{}, tThis{}, tElse{}), "Invalid option tag(s)!"); // ERROR! Invalid option tag(s)!

Is there any way to redefine a struct template with another number of parameters, such as in Attempt on Multiple Checks above?


Solution

  • Issue with

    template<typename TBase, typename TCandidate, typename... TRest>
    struct isBaseOf {
        isBaseOf() = delete;
        static const bool value = isBaseOf<TBase, TRest...>::value && 
                                  std::is_base_of<TBase, TCandidate>::value;
    };
    

    Is that a the end, you finish with:

    static const bool value = isBaseOf<TBase, /*Empty Pack*/>::value && 
                              std::is_base_of<TBase, TCandidate>::value;
    

    isBaseOf<TBase, TRest...> is invalid for empty pack.

    You have to add specialization to handle this case:

    template<typename TBase, typename TCandidate>
    struct isBaseOf<TBase, TCandidate> {
        isBaseOf() = delete;
        static const bool value = std::is_base_of<TBase, TCandidate>::value;
    };
    

    Alternative without recursion:

    template <bool... Bs> struct Bools{};
    template <bool... Bs> using All = std::is_same<Bools<true, Bs...>, Bools<Bs..., true>>;
    
    template<typename TBase, typename... TCandidates>
    using isBaseOf = All<std::is_base_of<TBase, TCandidates>::value...>;