Home > OS >  Template partial ordering and auto and reference NTTP: discrepancy between major compilers
Template partial ordering and auto and reference NTTP: discrepancy between major compilers

Time:12-29

Lifted from comments here.

Consider these templates and a function call:

template <int&> struct X{};
template <int& V> int func(X<V>) { return 0; }   
template <auto V> int func(X<V>) { return 1; }

int i;
int main() { return func(X<i>{}); }

The three major compilers differ in interpreting this code.

MSVC consider it ambiguous. GCC and Clang choose the first overload. Demo

However Clang considers the second overload non-viable, while GCC and MSVC do not. Demo

If we replace int& with int, both MSVC and Clang consider the call ambiguous, while gcc still chooses the first overload. Demo

Which compiler is right? Relevant quotes from the standard will be much appreciated.

CodePudding user response:

This is a msvc bug because the template non-type parameter with auto can never be deduced to a reference type. This in turn means that the overload with auto can only deduce the parameter to be int but then that will fail because template arguments must be constexpr which i is not.

This essentially means that the first overload with template <int& V> is the only option left because the second overload is not viable. So the program is well-formed and the first overload should be used.

This can also be seen from nontype template parameter documentation:

The type of a non-type template parameter may be deduced if it includes a placeholder type (auto, a placeholder for a deduced class type (since C 20), or decltype(auto)). The deduction is performed as if by deducing the type of the variable x in the invented declaration T x = template-argument;, where T is the declared type of the template parameter.

(emphasis mine)

This is basically telling us the x cannot be a reference type. And so the second overload(with auto) in your example is not viable(as i is not constexpr and T is deduced to be int) and there is only one version remaining(which is the first overload with int&) and it should be used for the call func(X<i>{}) in your original example.


You can also confirm this by removing the first overload so that the call func(X<i>{} will be rejected. Demo. Note that only clang rejects(correctly saying non-type template argument is not a constant expression) the call in the modified example while gcc and msvc incorrectly accepts it.

 template <int&> struct X{};
       
 template <auto V> int func(X<V>) { return 1; }

 int i;
 int main() 
 { return func(X<i>{}); //only clang correctly rejects this while gcc and msvc accepts this
 }

Here is a gcc bug for the modified example.

  • Related