Partial ordering of function templates - an ambiguous call

Consider this C ++ 11 code snippet:

#include <iostream> #include <cstddef> template<typename T> void f(T, const char*) //#1 { std::cout << "f(T, const char*)\n"; } template<std::size_t N> void f(int, const char(&)[N]) //#2 { std::cout << "f(int, const char (&)[N])\n"; } int main() { f(7, "ab"); } 

Okay, so ... what overload is chosen? Before releasing beans with compiler output, try to explain this.

(All section links are for the final standard document for C ++ 11, ISO / IEC 14882: 2011.)

T from # 1 is output to int , N from # 2 is output to 3 , both specializations are candidates, both are viable, so good. Which one is better?

First, the implicit conversions needed to map function arguments to function parameters are considered. For the first argument, conversion is not required in any case (identity conversion), int everywhere, so both functions are equally good. For the second type of argument, const char[3] , and two conversions:

  • for # 1, conversion of matrix to pointer, conversion of lvalue category, according to [13.3.3.1.1] ; this transformation category is ignored when comparing transformation sequences in accordance with [13.3.3.2] , so that it is basically the same as the identity transformation for this purpose;
  • for # 2, the parameter has a reference type and is bound directly to the argument, therefore, according to [13.3.3.1.4] , this is again an identity transformation.

Again, no luck: these two functions are still equally good. Both are specialization patterns, now we need to see which function pattern, if any, is more specialized ( [14.5.6.2] and [14.8.2.4] ).

EDIT 3: The description below is close, but not entirely accurate. See My answer for what I think is the correct process description.

  • The output of the template argument with No. 1 as the parameter and # 2 as the argument: we set the value of M to replace N , T , deduced as int , const char* , since the parameter can be initialized from an argument of type char[M] , everything is fine. As far as I can tell, # 2 is at least as specialized as # 1 for all types involved.
  • C # template template argument output as parameter and # 1 as argument: we invent type U to replace T , parameter of type int cannot be initialized from argument of type U (unrelated types) parameter of type char[N] cannot be initialized from argument of type const char* , and the value of the non-type N parameter cannot be inferred from the arguments, so ... everything fails. As far as I can tell, # 1 is not at least as specialized as # 2 for all types involved.

EDIT 1: The above has been edited based on comments from Columbo and dyp to reflect the fact that the links are deleted before trying to output the template argument in this case.

EDIT 2: Based on the information from hvd, top-level cv qualifiers are also deleted. In this case, this means that const char[N] becomes char[N] , because the cv determinants on the elements of the array also apply to the array itself (since the array of const also a const array ); this was not at all obvious in the C ++ 11 standard, but has been refined for C ++ 14.

Based on the foregoing, I would say that the partial order of function templates should choose # 2 as a more specialized one, and the call should solve it without any ambiguity.

Now back to the harsh reality. Both GCC 4.9.1 and Clang 3.5.0 with the following parameters

 -Wall -Wextra -std=c++11 -pedantic 

reject the call as ambiguous, with similar error messages. Error from Clang:

 prog.cc:16:2: error: call to 'f' is ambiguous f(7, "ab"); ^ prog.cc:4:27: note: candidate function [with T = int] template<typename T> void f(T, const char*) //#1 ^ prog.cc:9:30: note: candidate function [with N = 3] template<std::size_t N> void f(int, const char(&)[N]) //#2 ^ 

Visual C ++ 2013 IntelliSense (based on the EDG compiler, as far as I know) also places the call as ambiguous. Oddly enough, the VC ++ compiler goes ahead and compiles the code without errors, choosing # 2. (I agree with me, so it should be right.)

An obvious question for experts: why is the challenge ambiguous? What am I missing (in the area of ​​partial ordering, I would have guessed)?

+46
c ++ c ++ 11 templates ambiguous
Dec 19 '14 at 21:10
source share
2 answers

I give details of my current understanding of the problem as an answer. I am not sure that this will be the last word on this issue, but it can serve as the basis for further discussion, if necessary. The comments from dyp, hvd, and Columbo were needed to find the various bits of information mentioned below.

As I suspected, the problem is related to the rules for partial ordering of function templates. Section [14.8.2.4] (Derivation of template arguments in partial ordering) says that after preliminary transformations that remove links and cv-qualifiers, type inference is performed as described in [14.8.2.5] (inference from template arguments from type) . This section is different from the one that refers to function calls β€” it will be [14.8.2.1] (subtracting the template arguments from the function call).

When template parameters are inferred from function argument types, several special cases are allowed; for example, the template parameter T used in a function parameter of type T* can be inferred when the argument of the function is T[i] , because conversion from an array to a pointer is allowed in this case. However, this is not the inference process that was used in partial ordering , although we are still talking about functions.

I think that a simple way to think about the rules for outputting a template argument during partial ordering is to say that they are the same rules as for outputting template arguments when comparing class specialties.

Clean like dirt? Perhaps a few examples will help.

This works because it uses rules to infer template arguments from a function call:

 #include <iostream> #include <type_traits> template<typename T> void f(T*) { std::cout << std::is_same<T, int>::value << '\n'; } int main() { int a[3]; f(a); } 

and prints 1 .

This is not so because it uses rules to infer template arguments from a type:

 #include <iostream> template<typename T> struct A; template<typename T> struct A<T*> { static void f() { std::cout << "specialization\n"; } }; int main() { A<int[3]>::f(); } 

and the error from Clang is

 error: implicit instantiation of undefined template 'A<int [3]>' 

Specialization cannot be used because T* and int[3] do not match in this case, so the compiler tries to instantiate the primary template.

This is the second type of output that was used in partial ordering.




Back to the function template declarations:

 template<typename T> void f(T, const char*); //#1 template<std::size_t N> void f(int, const char(&)[N]); //#2 

My description of the partial sequencing process will be:

  • The output of the argument of the template C # 1 as a parameter and # 2 as an argument: we set the value of M to replace N , T is displayed as int , but the parameter of type const char* does not match the argument of type char[M] , therefore # 2 is not , at least as specialized as # 1 for the second pair of types.
  • The output of the C # 2 template argument as a parameter and # 1 as an argument: we invent type U to replace T , int and U do not match (different types), a parameter of type char[N] does not match an argument of type const char* , and the value of the parameter A non-type N pattern cannot be inferred from arguments, so # 1 is not at least as specialized as # 2 for any type pair.

Since in order to be selected, the template must be at least as specialized as the other for all types, it follows that none of the templates is more specialized than the other, and the call is ambiguous.




The explanation above is somewhat contrary to the description of a similar problem in Core Language Active Issue 1610 (link provided by hvd).

Example:

 template<class C> void foo(const C* val) {} template<int N> void foo(const char (&t)[N]) {} 

The author claims that, intuitively, the second template should be chosen as a more specialized one, and that this is currently not happening (no template is more specialized than the other).

He then explains that the reason is the removal of the const qualifier from const char[N] , which gives char[N] , which leads to output failure with the const C* parameter.

However, based on my current understanding, the deduction in this case will not end, const or no const . This is confirmed by current implementations in Clang and GCC: if we remove the const qualifier from the parameters of both function templates and call foo() with the argument char[3] , the call is still ambiguous. Arrays and pointers just don't match the current rules during partial ordering.

Having said that, I am not a member of the committee, so there may be more than I understand.




Update. I recently came across another active Core issue that dates back to 2003: issue 402 .

An example in it is equivalent to that located in 1610 . The remarks on the problem make it clear that the two overloads are disordered in accordance with the partial ordering algorithm in the form in which it exists, precisely because of the lack of decay rules for pointer matrices in partial ordering.

Last comment:

The view was expressed that it would be desirable to have this case, but we do not think that it is worth spending time on his work now. If at some point we consider some larger partial order changes, we will consider it again.

Thus, I am sure that the interpretation I gave above is correct.

+9
Dec 21 '14 at 23:52
source share
β€” -

Initially, I thought that the problem with your code was that you did not take into account the setting of the function type. Adjusting the type of a function causes an array with borders to be interpreted as a pointer to a type. I tried to find a solution to your problem by asking the compiler that it statically stats using templates, but instead I get more interesting results:

 #include <iostream> #include <type_traits> template<typename T, std::size_t N> void is_same( const T* _left, const char(&_right)[N] ) { typedef decltype(_left) LeftT; typedef decltype(_right) RightT; std::cout << std::is_same<LeftT,const char*>::value << std::endl; std::cout << std::is_same<LeftT,const char(&)[3]>::value << std::endl; std::cout << std::is_same<LeftT,const char(&)[4]>::value << std::endl; std::cout << std::is_same<RightT,const char*>::value << std::endl; std::cout << std::is_same<RightT,const char(&)[3]>::value << std::endl; std::cout << std::is_same<RightT,const char(&)[4]>::value << std::endl; } int main() { std::cout << std::boolalpha; is_same( "ab", "cd" ); return 0; } 

The way out gives: true false false false true false

The compiler is able to distinguish between arguments in this case.

Edit 1: Here is another code. The introduction of rvalue references makes functions more distinguishable.

 #include <iostream> // f template<typename _T> void f( _T, const char* ) { std::cout << "f( _T, const char* )" << std::endl; } template<std::size_t _kN> void f( int, const char(&)[_kN] ) { std::cout << "f( int, const char (&)[_kN] )" << std::endl; } // g template<typename _T> void g( _T, const char* ) { std::cout << "g( _T, const char* )" << std::endl; } template<std::size_t _kN> void g( int, const char(&&)[_kN] ) { std::cout << "g( int, const char (&&)[_kN] )" << std::endl; } // h template<std::size_t _kN> void h( int, const char(&)[_kN] ) { std::cout << "h( int, const char(&)[_kN] )" << std::endl; } template<std::size_t _kN> void h( int, const char(&&)[_kN] ) { std::cout << "h( int, const char (&&)[_kN] )" << std::endl; } int main() { //f( 7, "ab" ); // Error! //f( 7, std::move("ab") ); // Error! f( 7, static_cast<const char*>("ab") ); // OK //f( 7, static_cast<const char(&)[3]>("ab") ); // Error! //f( 7, static_cast<const char(&&)[3]>("ab") ); // Error! g( 7, "ab" ); // OK //g( 7, std::move("ab") ); // Error! g( 7, static_cast<const char*>("ab") ); // OK g( 7, static_cast<const char(&)[3]>("ab") ); // OK //g( 7, static_cast<const char (&&)[3]>("ab") ); // Error! h( 7, "ab" ); // OK (What? Why is this an lvalue?) h( 7, std::move("ab") ); // OK //h( 7, static_cast<const char*>("ab") ); // Error h( 7, static_cast<const char(&)[3]>("ab") ); // OK h( 7, static_cast<const char(&&)[3]>("ab") ); // OK return 0; } 
0
Dec 24 '14 at 7:28
source share



All Articles