I have the following code, see also live example:
template <typename A, typename B>
using ternary = decltype(true ? std::declval <A>() : std::declval <B>());
template <template <typename, typename> class F>
struct test
{
template <typename A, typename B, typename T = F <A, B> >
static std::true_type call(int);
template <typename A, typename B>
static std::false_type call(...);
};
template <template <typename, typename> class F, typename A, typename B>
using sfinae = decltype(test <F>::template call <A, B>(0));
template <typename T> struct X { };
template <typename T> struct Y
{
template <typename A>
Y(A&& a) { }
};
int main ()
{
using A = X <int>;
using B = X <double>;
using C = Y <int>;
using D = Y <double>;
sfinae <ternary, A, B>{}; // OK
sfinae <ternary, C, D>{}; // GCC error:
// operands to ?: have different types ‘Y<int>’ and ‘Y<double>’
}
It's the result of extreme simplification of actual code, so don't ask if it's useful. Roughly, sfinae <ternary, A, B>
does a pretty much standard SFINAE test on whether one may apply the ternary operator ?:
to two arguments of types A
, B
.
Clang compiles fine. GCC is OK with the first call using class template X
but gives an error on the second call using Y
. The error indicates that SFINAE fails and the compiler unexpectedly tries to apply the ternary operator, which it shouldn't. SFINAE should never fail (result in a compiler error): it should always compile, returning either true_type
or false_type
(in this example, it should always return false_type
).
The only difference between class templates X
, Y
is that Y
has constructor
template <typename A>
Y(A&& a) { }
In my actual code, there are more constructors and all are equipped with enable_if
to allow for disambiguation. To be just a bit more realistic, Y
would be
template <typename T> struct Y
{
using type = T;
template <
typename A,
typename = typename std::enable_if <
std::is_constructible<T, typename A::type>{}
>::type
>
Y(A&& a) : /*...*/ { }
};
to allow construction by other Y
's having different T
, but nothing would change with respect to the error. Normally the ternary operator should either fail due to different types or be ambiguous due to the generic constructor (which is the case?) but either way SFINAE should report false_type
.
How does this constructor make SFINAE fail? Is GCC compliant here? Any workaround?
EDIT
If I manually instantiate
decltype(true ? std::declval <C>() : std::declval <D>());
in main()
, clang says
error: conditional expression is ambiguous; 'Y<int>' can be converted to 'Y<double>'
and vice versa
while for A
, B
it says
incompatible operand types ('X<int>' and 'X<double>')
and GCC insists on the same error as above in both cases. I don't know if this may help.