Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can C++ 17 handle nested variadic templates? [duplicate]

Consider the C++ 17 code below, which tests a set of enum values to see if another enum value is contained in that set:

enum Flag { val1, val2, val3, val4, val5 };

template<Flag arg> struct Value {
    template<Flag... set> struct IsIn {
        static constexpr bool value =
            static_cast<bool>(((set == arg) || ...));
    };
};

This works as intended:

bool x = Value<val4>::IsIn<val1, val2, val5>::value;
// x == false

bool y = Value<val2>::IsIn<val3, val2>::value;
// y == true

However, I wish to test if all of a set of values are contained within another set, like so:

template<Flag... args> struct Values {
    template<Flag... set> struct AreIn {
        static constexpr bool value =
            static_cast<bool>((Value<args>::IsIn<set...>::value && ...));
    };
};

The above does not compile on GCC 7.3 or Clang 5.0; they both give rather cryptic answers that give little insight into the problem. Given that parameter pack expansion in a template parameter list is allowed (as long as the template supports the expansion), I'm having a hard time figuring out why this isn't legal C++.

like image 755
SumDood Avatar asked Feb 08 '18 07:02

SumDood


2 Answers

You've run into one of the most irritating problems in the C++ syntax: non-inferrable dependent names.

When you do Foo<Bar>::Baz<Quux>, since Foo<Bar> is a dependent name, you must put the template keyword before Baz in order to prevent the parser from running off a cliff. Clang is usually quite good at explicitly telling you about this with a helpful error, but in some cases (like yours) it just explodes and says Expected ) or something equally unhelpful.

For more information, see this other question

So, all you have to do to fix your template, is add the template keyword on the dependent template invocation:

template<Flag... args>
struct Values {
    template<Flag... set>
    struct AreIn {
        static constexpr bool value =
            static_cast<bool>((Value<args>::template IsIn<set...>::value && ...));
    };
};

Note, also, that the static_cast<bool>() is redundant.

like image 50
Chris Kitching Avatar answered Oct 31 '22 20:10

Chris Kitching


This solves your problem: replace

static_cast<bool>((Value<args>::IsIn<set...>::value && ... ))

by

static_cast<bool>((Value<args>::template IsIn<set...>::value && ... ))

Why is this? At the point of parsing the declaration of AreIn, the compiler can't know what possible types Value<args> might resolve to. Perhaps, at a later point in the file, you'd be going to declare a specialization of Values that does not contain a template subclass IsIn but rather a field of that name. Using the template keyword promises to the compiler that IsIn is expected to be some sort of template, so the following < ... > get parsed as template arguments, rather than comparison operators with set... and with ::value (a variable in the global namespace), which would also make sense.

Of course, one could ask why the compiler does not wait till it knows that you are using Values<val1, val2>, for which there indeed is a templated subclass AreIn<val1, val2, val5> which has a static member ::value. But parsing ahead and remembering a partially processed syntax tree is just how today's compilers work, and the standard makes the template hint mandatory for the above reasons.

like image 4
The Vee Avatar answered Oct 31 '22 20:10

The Vee