Is substitution failure an error with dependent non-type template parameters?
Asked Answered
E

1

10

Let's say I have these template aliases:

enum class enabler {};

template <typename T>
using EnableIf = typename std::enable_if<T::value, enabler>::type;
template <typename T>
using DisableIf = typename std::enable_if<!T::value, enabler>::type;

I can do the following in GCC:

#include <iostream>

template <typename T, EnableIf<std::is_polymorphic<T>> = {}>
void f(T) { std::cout << "is polymorphic\n"; }

template <typename T, DisableIf<std::is_polymorphic<T>> = {}>
void f(T) { std::cout << "is not polymorphic\n"; }

struct foo { virtual void g() {} };

int main() {
    f(foo {});
    f(int {});
}

It prints:

is polymorphic
is not polymorphic

Which matches my expectations.

With clang that code does not compile. It produces the following error messages.

test.cpp:11:58: error: expected expression
template <typename T, EnableIf<std::is_polymorphic<T>> = {}>
                                                         ^
test.cpp:14:59: error: expected expression
template <typename T, DisableIf<std::is_polymorphic<T>> = {}>
                                                          ^
test.cpp:20:3: error: no matching function for call to 'f'
  f(foo {});
  ^
test.cpp:12:6: note: candidate template ignored: couldn't infer template argument ''
void f(T) { std::cout << "is polymorphic\n"; }
     ^
test.cpp:15:6: note: candidate template ignored: couldn't infer template argument ''
void f(T) { std::cout << "is not polymorphic\n"; }
     ^
test.cpp:21:3: error: no matching function for call to 'f'
  f(int {});
  ^
test.cpp:12:6: note: candidate template ignored: couldn't infer template argument ''
void f(T) { std::cout << "is polymorphic\n"; }
     ^
test.cpp:15:6: note: candidate template ignored: couldn't infer template argument ''
void f(T) { std::cout << "is not polymorphic\n"; }
     ^
4 errors generated.

Should it compile? Which of the two compilers is faulty?

Ere answered 16/4, 2012 at 19:41 Comment(11)
Ooops, I feel silly. I have a feeling this has nothing to do with template aliases, so the title is possibly misleading :S Sorry about that, I'll investigate a bit and fix the title if that turns out the case.Ere
DisableIf<std::is_polymorphic<T>> = {} is that legal initializer list initialization? Can structures be template value parameters?Cropland
@Cropland No, structures can't. That's why I use an enum :)Ere
Clang issues similar error messages if I don't use the aliases and just manually "inline" them, so I fixed the title.Ere
Quickly scanning the standard, it seems to be legal, so it's probably an issue with clang. Does it still fail on clang if you change enabler to int and {} to 0?Cropland
BTW, the standard says you should have at least one value in your enum.Cropland
Out of curiosity, why do you want to pass an enum as the second argument to the template? Why not template <typename T, typename U = std::enable_if<std::is_polymorphic<T>>::type > void f( T ); ?Talkfest
@Cropland No, it doesn't say that. Check the grammar at §7.2, paragraph 1: the enumerator list is optional. Check also paragraph 6 for an explicit mention of an empty enumerator list.Ere
@DavidRodríguez-dribeas try it out on this code and you'll see. You'll end up redeclaring the same template with different default arguments, which is not allowed. More info on the thought process that led to this style of enable_if: loungecpp.wikidot.com/tips-and-tricks:enable-if-for-c-11Ere
@R.MartinhoFernandes: Oh yes, it does. Read 7.2 par. 2: The optional identifier shall not be omitted in declaration of a scoped enumeration.Cropland
@Cropland That's for the enum's name. It refers to the fact that enum {} is valid, but enum class {} isn't. Not at all relevant here.Ere
D
7

First and foremost, thanks to @Richard Smith on the #llvm IRC Channel on oftc for the explanation.
Unfortunately, this is not legal C++ and as such Clang is correct: {} is not an expression but a braced-init-list and as such will never be a constant expression as is needed in the initializer of a non-type template parameter.

§14.3.2 [temp.arg.non-type] p1

A template-argument for a non-type, non-template template-parameter shall be one of:

  • for a non-type template-parameter of integral or enumeration type, a converted constant expression (5.19) of the type of the template-parameter; or
  • [...]

One solution would be a dummy value in enabler.

Deutero answered 16/4, 2012 at 20:54 Comment(5)
That is not a good argument - we are not talking about template arguments here (noone tries eg. f<int,{}>), but default arguments, which have a syntax of a parameter-declaration, which can, in principle, have {} on the rhs (and if enabler{} is a constant expression, or x, given enabler x{}, there shouldn't be a problem with constant). However, 8.3.6/3 says there should be an expression in case of template parameter declaration.Cropland
@jpalecek: "oone tries eg. f<int,{}>" Uh, calling f<int> does exactly that.Stockpile
@GManNickG: No, it doesn't. Default parameters are not specified (at least explicitly) as text substitutions.Cropland
@jpalecek: In what way does a default argument differ from a formal argument? In any case you can't just use {} because it can never be a constant expression, which is what's needed to initialize a non-type parameter.Deutero
@Cropland Is paragraph 9 of 14.1 Template Parameters "A default template-argument is a template-argument (14.3) specified after = in a template-parameter." the missing argument in this case?Agraphia

© 2022 - 2024 — McMap. All rights reserved.