Reputation: 14064
What follows is a simplified version of a problem I hit with real code.
Short version: just look at the code and error at gcc.godbolt.org / long version: read on ;)
Suppose I want a class with a template parameter setting
and a method int func(int)
such as:
setting
is false
, func
returns its argumentsetting
is true
, func
doubles its argumentThe simplest way to do that is to specialize the class template:
template<bool setting> struct A {
int func(x) const { return 2 * x; }
};
template<> struct A<false> {
int func(x) { return x; }
};
The problem with this approach is that if I have a bunch of other methods that don't depend on setting
, I'll have to copy-paste them in both specialization (or inherit from a common base, when there's not too much inter-dependencies).
So instead, I can use SFINAE to select the right method, e.g. with std::enable_if
. This requires the method to have a template argument, because substitution failure must invalidate just the method, not the whole class. As far as I know, the failure can occur in either of:
Here's the code using the method's arguments:
template<bool setting> struct B {
template<bool when=true>
int func(int x
, typename std::enable_if<when && setting>::type * u=0
)
{ return 2 * x; }
template<bool when=true>
int func(int x
, typename std::enable_if<when && !setting>::type * u=0
)
{ return x; }
};
And here's the version using the method's template arguments:
template<bool setting> struct C {
template<bool when=true, typename std::enable_if<
when && setting
>::type...>
int func(int x) { return 2 * x; }
template<bool when=true, typename std::enable_if<
when && !setting
>::type...>
int func(int x) { return x; }
};
I tend to prefer the last version, as it makes the method's signature more readable, but that's a matter of personal taste.
My question concerns this last version: is it valid C++ ? gcc compiles it fine, but clang does not (tested with -std=c++11
/ c++1y
/ c++1z
with same results). The class definition in itself compiles OK, but the error occurs when it's instantiated:
int main() {
A<true> a;
B<true> b;
C<true> c;
return a.func(1) + b.func(2) + c.func(3);
}
compiles in gcc 5.3 but not with clang 3.7.1:
test.cpp:30:36: error: call to member function 'func' is ambiguous
return a.func(1) + b.func(2) + c.func(3);
~~^~~~
test.cpp:20:7: note: candidate function [with when = true, $1 = <>]
int func(int x) { return 2 * x; }
^
test.cpp:23:7: note: candidate function [with when = true, $1 = <>]
int func(int x) { return x; }
^
1 error generated.
So is this valid C++ ? Is it a clang bug or is gcc wrong in accepting this code ?
Upvotes: 4
Views: 250
Reputation: 217235
Is SFINAE forbidden in template arguments
It is valid. You may do for example:
template<bool setting> struct C {
template<bool when=true, typename std::enable_if<
when && setting
>::type* = nullptr>
int func(int x) { return 2 * x; }
template<bool when=true, typename std::enable_if<
when && !setting
>::type* = nullptr>
int func(int x) { return x; }
};
The problem with typename std::enable_if<when && !setting>::type...
should be related to CWG 1558.
And so your code should be correct in C++17.
Upvotes: 3