C++ 是否对参与偏序的类型执行实例化

C++ 是否对参与偏序的类型执行实例化,c++,templates,c++17,language-lawyer,partial-ordering,C++,Templates,C++17,Language Lawyer,Partial Ordering,最近,我发现,GCC改变了偏序期间的行为,具体情况如下: #a |--------|------------------------------------------| | P (#2) | A (#1) | |--------|------------------------------------------| | int | typename unknow_context<UniqueA>

最近,我发现,
GCC
改变了偏序期间的行为,具体情况如下:

#a  
|--------|------------------------------------------|
| P (#2) | A (#1)                                   |   
|--------|------------------------------------------|  
| int    |  typename unknow_context<UniqueA>::type  |
|--------|------------------------------------------|   
| T      | UniqueB                                  |   
|--------|------------------------------------------|  
#包括
模板
结构未知上下文{
使用type=int;
};
模板
void show(typename unknow_context::type,U){//candidate#1
std::cout(由于这个答案与OP一致,并且与Clang和GCC(trunk)的实现不一致,它可能是一个有点不完整的答案,但至少它强调了偏序规则存在的一些问题,特别是对于涉及非推导上下文的偏序)


问题1:哪个编译器是正确的

让我们首先注意到,在GCC 11(/trunk)中,两个编译器都同意他们的解释,并选择候选函数#2作为比候选函数#1更专业的函数,并根据重载解析选择前者作为最佳可行函数

然而,你的论点似乎是正确的,特别是强调:

[…]如果特定的
p
不包含参与模板参数推断的模板参数,
p
不用于确定顺序。

意思是

[…]用于确定顺序的每对类型

不应考虑<代码>(p,a)/代码>配对>代码>(int,type NoNeNo.NojyCurry::type)< /Cord>排序,其余候选对1至少与候选“2”相同,这意味着候选者“1”至少被指定为候选者“2”。 因此,我认为Clang是错误的,根据GCC 11(/trunk),GCC再次是错误的,但正如我在下面强调的那样,涉及非推断上下文的边缘情况下的偏序规则在历史上一直没有得到充分的规定(其中许多都得到了解决),而现在,至少是模糊的(可能仍然没有得到充分的规定),正如我们看到的,它们的实现差异很大


问题2:是否在偏序期间执行专门化的实例化

我不确定最相关的部分是什么;可以说它属于

除非需要实例化,否则实现不应隐式实例化[…],

以及以下非规范性说明:

[ 注意:对类型和表达式的替换可能会产生诸如类模板专门化和/或函数模板专门化的实例化之类的效果,[…]结束注释]

但是,是的,
unknown_context
的专门化的合理实例化将被要求作为偏序中推导参数的模板参数替换的一部分。reje说,我们可以使用注入的friend技巧强制可诊断的ODR冲突,如果这对编译器是正确的,并且GCC和Clang都同意正在执行以下程序:

// 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.
}
//由于注入的好友,identity类可能会
//只能在给定TU内实例化一次,或
//程序,可诊断([basic.odr.def]/1)。
模板
结构标识{
使用类型=T;
友元void f(){}
};
无效f();
模板
void show(typename标识::type,U){}
模板
void show(T,T){}
int main(){
标识i;//f()现在已定义
f();//好的
show(0,0);//错误:将f()重新定义为
//偏序替换。
}
使用指示错误:

错误:重新定义“f”
友元void f(){}
^
注意:在模板类的实例化中
此处请求“标识”
void show(typename标识::type,U){}
注意:替换导出的模板参数时
进入函数模板“show”[U=int]

偏序和非推断上下文中的历史不一致性 我们可以从激活/打开开始:

455.偏序和非推导参数

目前还不清楚重载和偏序如何处理对应参数的非推导对

[……]

约翰·斯派塞:在偏序规则中是否正确处理了非派生上下文可能(也可能不)存在一个问题

请注意,编译器,尤其是Clang和GCC,在涉及非推断上下文的边缘情况下如何应用偏序规则的问题上一直存在分歧

GCC的Jason Merrill写道,这篇文章被标记为CWG第455期的副本。Jason活跃在许多公开的GCC错误报告中,特别是在

  • -具有依赖类型的非类型模板参数的偏序
那[强调我的]

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

类似地,G++(和EDG)拒绝,而clang接受

这似乎是标准中未指定的区域。

以及

  • [8/9/10/11回归]错误“部分专业化不比”错误

这实际上是一个偏序问题;[…]

GCC至少从4.1开始就拒绝了这一点,因为它不明确。EDG/icc也拒绝了这一点。clang和msvc也接受了这一点,就像最初的测试用例一样

问题在于#2中#1的偏序推导:我们从第二个参数中推导出U的int,从第三个参数中推导出U的Id::type,但这些都不一致,因此第三个参数的推导在两个方向都失败,函数也不明确

这与公开核心问题455和1337有关。

我不知道clang/msvc用什么理由得出结论说#2更专业。

因此,根据上面的引文,他们在历史上有着不同的解释
|----|------------------------------------------------------------|
|P   |A                                                           |    
|----|------------------------------------------------------------|  
|T   |typename unknow_context<UniqueA>::type /*Is it equivalent to| 
|    | UniqueA? */                                                |  
|----|------------------------------------------------------------|   
|T   |UniqueA                                                     |  
|----|------------------------------------------------------------|  
// 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.
}
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]
#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);  
}