Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the instantiation be performed for the type that participate in partial ordering

Recently, I find that GCC has changed the behavior during partial ordering, the concrete case is the following:

#include <iostream>
template<class T>
struct unknow_context{
    using type = int;
};
template<class U>
void show(typename unknow_context<U>::type, U){ // candidate #1
    std::cout<<"#1\n";
}

template<class T>
void show(int, T){   // candidate #2
    std::cout<<"#2\n";
}
int main(){
    show(0,0);  
}

The result is, Clang prints #2(any version of Clang has printed a consistent result). However, GCC has different behavior. The older version of GCC prints #2, instead, the latest GCC complains the candidate functions are ambiguous. Let's see what the standard says about partial ordering.
temp.deduct.partial#2

The deduction process uses the transformed type as the argument template and the original type of the other template as the parameter template. This process is done twice for each type involved in the partial ordering comparison: once using the transformed template-1 as the argument template and template-2 as the parameter template and again using the transformed template-2 as the argument template and template-1 as the parameter template.

So, we can get two sets of P/A pair for the candidate #1 and #2, respectively. One is the transformed #1 as A, the original #2 as P. The other is the original #1 as P, the transformed #2 as A. So the two sets will be given as the following:

#a  
|--------|------------------------------------------|
| P (#2) | A (#1)                                   |   
|--------|------------------------------------------|  
| int    |  typename unknow_context<UniqueA>::type  |
|--------|------------------------------------------|   
| T      | UniqueB                                  |   
|--------|------------------------------------------|  

T can be deduced from UniqueB. For the first set, the rule says:

If a particular P contains no template-parameters that participate in template argument deduction, that P is not used to determine the ordering.

So, we put them aside first and consider them later.

#b  
|----------------------------------|-------|
|P (#1)                            |A (#2) |   
|----------------------------------|-------|  
| typename unknow_context<U>::type |int    |  
|----------------------------------|-------|
| U                                |UniqueA|  
|----------------------------------|-------|

For a non-deduced context, its value can be obtained from elsewhere.

In certain contexts, however, the value does not participate in type deduction, but instead uses the values of template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.

So, we can ignore the pair typename unknow_context<U>::type / int, and consider U/ UniqueA. U can be deduced from UniqueA.

If deduction succeeds for a given type, the type from the argument template is considered to be at least as specialized as the type from the parameter template.

From #b, we get the result that #2 is at least as specialized as #1.

The question is in the #a set. The second pair has no problem, we can say that the second part of #1 is at least as specialized as the second part of #2.

However, the rule for whether a function template F is more specialized than function template G is defined as:

Function template F is at least as specialized as function template G if, for each pair of types used to determine the ordering, the type from F is at least as specialized as the type from G. F is more specialized than G if F is at least as specialized as G and G is not at least as specialized as F.

So, we take attention to pair int / typename unknow_context<UniqueA>::type, although the rule says "P is not used to determine the ordering". However, an important note in the standard says:

[ Note: Under [temp.deduct.call] and [temp.deduct.partial], if P contains no template-parameters that appear in deduced contexts, no deduction is done, so P and A need not have the same form.  — end note ]

So, as far as now, from the P/A set #a, #1 is still at least as specialized as #2(deduction success). So, I think the latest GCC should be correct (ambiguous, neither is more specialized than the other).

Question 1:

which compiler is correct?

Question 2:

whether the instantiation of specialization be performed during partial ordering? The standard seems to not specify whether the instantiation will be performed.

#include <iostream>
template<class T>
struct unknow_context{
    using type = T;
};
template<class U>
void show(typename unknow_context<U>::type, U){
    std::cout<<"#1\n";
}

template<class T>
void show(T, T){
    std::cout<<"#2\n";
}
int main(){
    show(0,0);
}

All chose #2. I'm concerned about the particular P/A pair thereof, that is:

|----|------------------------------------------------------------|
|P   |A                                                           |    
|----|------------------------------------------------------------|  
|T   |typename unknow_context<UniqueA>::type /*Is it equivalent to| 
|    | UniqueA? */                                                |  
|----|------------------------------------------------------------|   
|T   |UniqueA                                                     |  
|----|------------------------------------------------------------|  

whether typename unknow_context<UniqueA>::type will be calculated to UniqueA? It seems that All compiler treat typename unknow_context<UniqueA>::type as a unique type rather than calculate it to UniqueA.

like image 693
xmh0511 Avatar asked Oct 27 '22 15:10

xmh0511


1 Answers

(As this answer agrees with OP and disagrees with the implementations of both Clang and GCC (trunk), it may be a somewhat incomplete answer, but at the very least it highlights some existing issues with the partial ordering rules, particularly for partial ordering where non-deduced contexts are involved))


Question 1: which compiler is correct?

Let's first note that as of GCC 11(/trunk), both compilers agree with their interpretation, and pick candidate #2 as more specialized than candidate #1, and as per [over.match.best]/1.7 overload resolution chooses the former as the best viable function.

However, your argument of [temp.func.order] seems valid, particularly the emphasis on [temp.deduct.partial]/4:

[...] If a particular P contains no template-parameters that participate in template argument deduction, that P is not used to determine the ordering.

meaning that

[...] for each pair of types used to determine the ordering [...]

in [temp.deduct.partial]/10 should not consider the (P, A) pair (int, typename unknow_context<UniqueA>::type) for ordering, and for the remaining pair candidate #1 is at least as specialized as candidate #2, meaning candidate #1 is at least a specified as candidate #2 as per [temp.deduct.partial]/10.

Thus, I believe Clang is wrong, and that GCC is once again wrong as per GCC 11(/trunk), but as I highlight below, the partial ordering rules in edge-cases where non-deduced contexts are involved have been, historically, underspecified (02-0051/N1393 addressed many of them), and nowadays, at the very least vague (possibly still underspecified), as we see a lot implementation variance over them.


Question 2: whether the instantiation of specialization be performed during partial ordering?

I am not sure of the most relevant section; it could arguably fall under [temp.inst]/9,

An implementation shall not implicitly instantiate [...], unless such instantiation is required.

and the non-normative note of [temp.deduct]/8:

[ Note: The substitution into types and expressions can result in effects such as the instantiation of class template specializations and/or function template specializations, [...] end note ]

but yes, reasonably instantiation of the specialization of unknown_context would be required as part of template argument substitution of deduced arguments as part of partial ordering. We can use the injected friend trick to force a diagnosable ODR-violation if this holds true for the compiler's, and both GCC and Clang agrees, rejecting the following program:

// Due to the injected friend, the identity class may 
// only be instantiated once within a given TU, or 
// the program, diagnosable ([basic.odr.def]/1). 
template<class T>
struct identity {
    using type = T;
    friend void f() {}
};
void f();

template<class U>
void show(typename identity<U>::type, U) {}

template<class T>
void show(T, T) {}

int main(){
    identity<char> i;  // f() now defined
    f();               // OK
    show(0,0);         // error: redefines f() as part of 
                       //        substitution in partial ordering.
}

with the instructive error:

error: redefinition of 'f'
       friend void f() {}
            ^
note: in instantiation of template class 
      'identity<int>' requested here
       void show(typename identity<U>::type, U) {}

note: while substituting deduced template arguments 
into function template 'show' [with U = int]

Historical unclarities in partial ordering and non-deduced contexts

We may start by the active/open CWG issue 455:

455. Partial ordering and non-deduced arguments

It's not clear how overloading and partial ordering handle non-deduced pairs of corresponding arguments.

[...]

John Spicer: There may (or may not) be an issue concerning whether nondeduced contexts are handled properly in the partial ordering rules

and note that compilers, notably Clang and GCC, consistently disagree on how to apply partial ordering rules in edge-cases where non-deduced contexts are involved.

Jason Merrill from GCC wrote CWG issue 1337, which was marked as a duplicate of CWG issue 455. Jason are active in a number of open GCC bug reports, particularly noting on

  • Bug 86193 - Partial ordering of non-type template parameters with dependent types

that [emphasis mine]

Jason Merrill 2018-06-18 19:09:13 UTC

which, similarly, G++ (and EDG) rejects, and clang accepts. I think G++ is right here: [...]

This does seem like an underspecified area in the standard.

as well as to

  • Bug 91133 - [8/9/10/11 Regression] Wrong "partial specialization is not more specialized than" error

that

This is really a question of partial ordering; [...]

This was rejected as ambiguous by GCC going back at least to 4.1. It is also rejected by EDG/icc. It is accepted by clang and msvc, like the original testcase.

The issue is with the partial ordering deduction of #1 from #2: we deduce int for U from the second argument, and Id::type for U from the third argument, and those don't agree, so deduction for the third argument fails in both directions, and the functions are ambiguous.

This is related to open core issues 455 and 1337.

I don't know what rationale clang/msvc are using to conclude that #2 is more specialized.

Thus, as per the quotes above, their historically different interpretations of the (possible underspecified) standard seems to have been intentional, though; such implementation variance is typically a sign of vagueness in the related segment of the standard, at best (underspecified, at worst).

See also GCC bug report 67228.


Was the ambiguity error from GCC just a regression?

The older version of GCC prints #2, instead, the latest GCC complains the candidate functions are ambiguous.

As noted above, GCC:s behaviour for the following program

#include <iostream>
template<class T>
struct unknow_context{
    using type = int;
};
template<class U>
void show(typename unknow_context<U>::type, U){ // candidate #1
    std::cout<<"#1\n";
}

template<class T>
void show(int, T){   // candidate #2
    std::cout<<"#2\n";
}
int main(){
    show(0,0);  
}

is as follows:

  • up until and including GCC 7.3.0: prints #2 (same as clang)
  • GCC 8/9/10: error: ambiguous call
  • GCC trunk/11: back to printing #2

I have not found an a bug reports for a [8/9/10] regression of this, but it seems GCC is now back to using the same interpretation as Clang (accepting the program and finding #2 as more specialized), which both of us seem to agree is wrong (candidate #1 should be considered at least as specialized as candidate #2).

like image 84
dfrib Avatar answered Nov 15 '22 09:11

dfrib