0

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?

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
Smartskaft2
  • 468
  • 3
  • 16

2 Answers2

1

In c++17, you can use a fold-expression over the && operator to achieve this

template<typename Base, typename ...Candidates>
struct is_base_of_multiple {
    static constexpr bool value = (std::is_base_of_v<Base, Candidates> && ...); // change std::is_base_of_v to your own implementation
};

If you cannot use c++17, but can use c++11, here is another way of doing it using just variadic templates

template <typename Base, typename First, typename ...Rest>
struct is_base_of_multiple {
    static constexpr bool value = std::is_base_of<Base, First>::value && is_base_of_multiple<Base, Rest...>::value;
};

template <typename Base, typename Candidate>
struct is_base_of_multiple<Base, Candidate> {
    static constexpr bool value = std::is_base_of<Base, Candidate>::value;
};
sparik
  • 1,181
  • 9
  • 16
  • 1
    Yeah this was what I tried first, but forgot about the `` for the template specialization, making it another declaration. Thus, my error warnings. I though I did i correct, but did not think it through enough. What we make here is a templated template specialization. Woah, a mouthful! – Smartskaft2 May 03 '20 at 18:17
  • 1
    Also, thank you for noticing my `const` error in of the `value` member. I of course wanted it to be `constexpr` as well. – Smartskaft2 May 03 '20 at 18:18
  • You're welcome :) @Jarod42 's non-recursive version is of course nicer, and is c++11 compliant too – sparik May 03 '20 at 18:47
  • 1
    He helped me get rid of my errors while implementing his solution as well. I think he deserves the answer, even though I like yours as well. Thank you! – Smartskaft2 May 03 '20 at 18:54
1

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...>;
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Or, I might implement a compatibility version of `std::conjuction` (also C++17) to use in place of your `All`. – aschepler May 03 '20 at 12:12
  • @aschepler: `std::conjunction` should be recursive normally to handle short circuit though. – Jarod42 May 03 '20 at 12:16
  • Yes, and it takes types, not Boolean constants. Just a different approach. – aschepler May 03 '20 at 12:19
  • @aschepler: With template, we might care about number of instantiations, as it impact compilation time. Recursive way does so O(n) instantiations (of `IsBaseOf`), whereas mine does `O(1)`. `std::is_base_of` still need to be instantiated O(n) time (according to OP's usage: `static_assert` so all true. SFINAE with "frequent" `tElse` type in first parameter might change the decision though). – Jarod42 May 03 '20 at 12:36
  • As I mentioned, the "three type template" was working together with the "two type template". The only difference between that and yours is the `` in your signature. That however, made all the difference! I guess what I tried with was a template declaration, but yours is a template specialization of the "three type template". Thanks! I have also taken your suggestion of making it work without recursion. Though I think that forces me to make my own `std::true_type` and `std::false_type`. But it's all good fun, eh? ;) – Smartskaft2 May 03 '20 at 17:25
  • Classes cannot be overloaded, functions can. Both can be fully specialized. Only classes can be partially specialized. `true_type`/`false_type` seems the base for type_traits (and easy to implement). – Jarod42 May 03 '20 at 17:34
  • Yeah, it was not that difficult. However, I am unsure if my current issue is related to some error I have or that the compiler is not modern enough. I tried using your last example, `using isBaseOf = All<...`. My compiler wants the parameter pack earlier ( error: expected parameter pack before '...'). I might have done something wrong, but it is basically stolen right off cppreference.com – Smartskaft2 May 03 '20 at 18:10
  • Code is C++11 compliant [Demo](https://godbolt.org/z/W9uAPQ). So no typo on my part :) – Jarod42 May 03 '20 at 18:17
  • @Jarod42 do you mind taking a quick look at the issues I get with my code? If not, I understand. I pasted what I have into it, and I get some other error messages than I get using GCC-AVR (might still not work in the end, everything from C++11 is not in there as far as I know). [Demo](https://godbolt.org/z/XidmWq) – Smartskaft2 May 03 '20 at 18:32
  • You miss definition of `std::is_base_of` which uses `decltype` around function call, [Demo](https://godbolt.org/z/kXjsGt) – Jarod42 May 03 '20 at 18:41
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/213040/discussion-between-smartskaft2-and-jarod42). – Smartskaft2 May 03 '20 at 18:45