Consider the following example:
template<int i>
struct nice_type;
template<class T>
struct is_nice : std::false_type {};
template<int i>
struct is_nice< nice_type<i> > : std::integral_constant<int, i> {};
template<class T, class = void>
struct pick
{
typedef std::integral_constant<int, -1> type;
};
template<class T>
struct pick<T, typename std::enable_if< is_nice<T>::value >::type >
{
typedef std::integral_constant<int, is_nice<T>::value > type;
};
int main()
{
std::cout << pick<int>::type::value << ", ";
std::cout << pick< nice_type<42> >::type::value << std::endl;
return 0;
}
Clang (3.4.1) outputs "-1, -1", while GCC(4.9.0) outputs "-1, 42".
The problem lays in the specialization of pick
. While Gcc seems happy to convert is_nice<T>::value
(42) to bool(true)
, clang does not do so, and discards the specialization. Both examples compiled with -std=c++11
.
Which compiler is right?
This is gcc bug 57891. Conversion of the integral constant 42
to bool
involves a narrowing conversion, which is not allowed in non-type template arguments. Hence the enable_if
is ill-formed, and the pick
specialization should be discarded, as clang correctly does.
§14.3.2/5 [temp.arg.nontype]
The following conversions are performed on each expression used as a non-type template-argument. If a non-type template-argument cannot be converted to the type of the corresponding template-parameter then the program is ill-formed.
— For a non-type template-parameter of integral or enumeration type, conversions permitted in a converted constant expression (5.19) are applied....
§5.19/3 [expr.const]
...
A converted constant expression of typeT
is an expression, implicitly converted to a prvalue of typeT
, where the converted expression is a core constant expression and the implicit conversion sequence contains only user-defined conversions, lvalue-to-rvalue conversions (4.1), integral promotions (4.5), and integral conversions (4.7) other than narrowing conversions (8.5.4).
§8.5.4/7 [dcl.init.list]
A narrowing conversion is an implicit conversion
...
— from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression whose value after integral promotions will fit into the target type.
This minimal example demonstrates the gcc bug:
template<bool>
struct foo{};
foo<10> f;
int main() {}
gcc-4.9 accepts the code while clang-3.4 rejects it with the following error:
error: non-type template argument evaluates to 10, which cannot be narrowed to type 'bool' [-Wc++11-narrowing]
foo<10> f; ^
The fix to your particular problem is easy. Make sure the non-type template argument to enable_if
evaluates to a bool
template<class T>
struct pick<T, typename std::enable_if< is_nice<T>::value != 0 >::type >
// ^^^^^^
{
typedef std::integral_constant<int, is_nice<T>::value > type;
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With