6

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++.

SumDood
  • 294
  • 1
  • 7

2 Answers2

6

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.

PaperBirdMaster
  • 12,806
  • 9
  • 48
  • 94
Chris Kitching
  • 2,559
  • 23
  • 37
  • Ah, I'm familiar with that concept when using the `typename` keyword; didn't know about when to use the `template` keyword. I fought this one for a few hours before giving up to ask here... thanks a ton! – SumDood Feb 10 '18 at 02:00
4

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.

The Vee
  • 11,420
  • 5
  • 27
  • 60