当模板参数推导是带有默认参数的类模板时,如何对函数模板参数进行模板推导

时间:2020-07-13 13:14:36

标签: c++ templates language-lawyer

template<typename T, typename U = T>
struct Test{};

template<typename T>
void func(Test<T>){  //#1
}

int main(){
  func(Test<int>{});  //#2
}

考虑以上代码,在调用函数模板func时,参数类型为Test<int,int>,调用函数模板时,将执行模板参数推导。

函数调用的模板参数推导规则是:
temp.deduct#call-1

模板自变量推导是通过将每个包含参与模板自变量推论的模板参数的函数模板参数类型(称为P)与调用的相应自变量的类型(称为A)进行比较来完成的,如下所述。

我非常确定A的类型为Test<int,int>,但是我不确定这里P的类型是什么。是Test<T>还是Test<T,T>,根据规则,看来P的类型在这里是Test<T>,然后执行推导过程以确定{{ 1}}参与模板参数推导。然后根据这些规则描述如下:

temp.deduct#call-4

通常,推论过程会尝试查找使推论的A与A相同的模板参数值(在如上所述对类型A进行转换之后)。

temp.deduct#5

从默认模板参数推导或获得所有模板参数后,模板的模板参数列表和函数类型中所有对模板参数的使用都将替换为相应的推导或默认参数值。

因为类模板T具有默认参数,所以推论的Test被替换为默认参数。这意味着推导的TA,它与参数类型Test<int,int>相同。

不过,这只是我的理解。我不确定Test<int,int>是什么类型。如果将函数参数的类型更改为P,则outcome将报告:

Test<int,double>

结果看起来像candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs. 'double') P,而Test<T,T>的第一值与T的第二个值冲突。

所以,我的问题是:

这里的TP还是Test<T>?为什么?

4 个答案:

答案 0 :(得分:1)

不是语言律师的答案

没有类型Test<T>实际上是Test<T, T>的“简写”。

就像使用默认函数参数一样,如果您拥有int foo(int a, int b = 24),则函数的类型为int (int, int),而诸如foo(11)之类的调用实际上为foo(11, 24)

答案 1 :(得分:0)

while my_timer>0: if SingleOrDouble=="Single": while answer=="yes" or answer=="Yes": while lives!=0 and n!=guess: guess = int(input("Enter an integer from 1 to 99: ")) if guess < n: print("guess is low") lives-=1 print("You have "+ str(lives) +" lives left.") elif guess > n: print ("guess is high") lives-=1 print("You have "+ str(lives) +" lives left.") else: print("You guessed it!") print("You won with "+ str(lives) +" lives left.") break print("Game over, the number was "+ str(n)) answer = input("Do you want to play again?") if answer=="yes" or answer=="Yes": lives=8 else: lives=16 while answer=="yes"or answer=="Yes": while lives!=0 and n!=guess: if lives%2==0: guess = int(input("Player 1 please Enter an integer from 1 to 99: ")) else: guess = int(input("Player 2 please Enter an integer from 1 to 99: ")) if guess < n: print("guess is low") lives-=1 print("You have "+str(lives//2)+" lives left.") elif guess > n: print ("guess is high") lives-=1 print("You have "+ str(lives//2) +" lives left.") else: if lives%2==0: print("Player 1 guessed it!") print("You won with "+ str(lives//2) +" lives left.") Gamewinner=2 else: print("Player 2 guessed it!") print("You won with "+ str(lives//2) +" lives left.") Gamewinner=1 break if Gamewinner>0: print("Game Over! Player "+str(Gamewinner)+"the number was "+ str(n)) else: print("Game Over! The number was "+ str(n)) answer = input("Do you want to play again?") if answer=="yes" or answer=="Yes": lives=8 sleep(1) if my_timer==0: break 必须是类型而不是模板。 P是模板ID,但标准中并未明确指出模板ID test <T>等效于test <T>。唯一要说的是:

模板ID是否有效

  • [...]
  • 每个不可推论的非打包参数都有一个参数,该参数没有默认的模板参数[...]

之后,通过使用默认一词来定位我们的直觉,填补了标准中的漏洞。

我认为关键是模板指定一个家庭,而模板ID无法指定一个家庭。

答案 2 :(得分:0)

这里的PTest<T>还是Test<T,T>?为什么?

PTest<T,T>


我认为我们可以同意[temp.deduct]的规则也适用于类模板;例如[temp.class.order]涵盖了类模板专业化的部分排序,完全基于以下概念:将类模板重写为(发明的)功能模板,并将功能模板的规则应用于与该模板相对应的发明的功能模板的规则。部分排序分析下的原始类模板。结合类模板的标准段落与功能模板相比非常简短的事实,我将以下参考解释为也适用于类模板。

现在,来自[temp.deduct]/1 [强调我的]:

当引用功能模板专业化 时,所有模板参数均应具有值。可以明确指定值,或者在某些情况下,可以根据使用情况推断出值,或者从默认值中获取 模板参数。 [...]

并且来自[temp.deduct]/2 [强调我的]:

指定了显式模板参数列表时,模板参数必须与模板参数列表兼容,并且必须产生如下所述的有效函数类型;否则类型推导将失败。具体来说,当针对给定的功能模板评估显式指定的模板参数列表时,将执行以下步骤:

  • (2.1)指定的模板参数必须与实物(即类型,非类型,模板)的模板参数匹配。除非[...]
  • ,否则参数不得超过

特别强调“ 被引用 ”和指定的模板参数;不需要为给定的匹配函数(/类)模板指定 all 参数,只有确实指定的参数遵循[temp.deduct] /的要求2用于明确指定的模板参数。

这导致我们返回给定候选功能/类模板的其余模板参数的[temp.deduct] / 1:可以推论(功能模板)或从默认模板参数获取。因此,呼叫:

func(Test<int>{});
根据以上参数,

在语义上等同于

func(Test<int, int>{});

的主要区别在于,前者的模板参数由显式指定的模板参数和默认模板参数决定,而后者的模板参数均由显式指定的模板参数决定。由此可见,ATest<int, int>,但是我们将为P使用类似的参数。


来自[temp.deduct.type]/3 [强调我的]:

给定类型P可以由许多其他类型,模板和非类型值组成:

  • [...]
  • (3.3)类型是类模板的专门化(例如A<int>包括类型,模板和非类型值由专业化的模板参数列表引用

请注意,[temp.deduct.type] /3.3中的描述现在返回到模板类型 P的模板参数列表P无关紧要,因为在以重载分辨率检查此特定候选函数时,通过部分显式指定模板参数列表并部分依赖默认模板参数(其中后者是实例化)来引用类模板-依赖。重载解析的这一步骤并不意味着任何实例化,仅意味着对候选对象的检查。因此,在这种情况下,当我们引用A(通过P)时,Test<int, int>适用于上面模板参数Test<int>的相同规则,{ {1}}是P,并且我们对于Test<int, int>P有一个完美的匹配(对于这个的单个参数参数对AP例子)


编译器错误消息?

根据上面的论点,可以说OP的失败示例可能会出现类似的错误消息:

A

至于以下简单的一个:

// (Ex1)
template<typename T, typename U = T>
struct Test{};

template<typename T>
void func(Test<T>) {}

int main() {
    func(Test<int, double>{});
}

事实并非如此,因为前者分别对GCC和Clang产生以下错误消息:

// (Ex2)
struct Foo {};
template<typename T> struct Test {};
template<typename T> void f(T) {}

int main() {
    f<Test<int>>(Test<Foo>{});
}

而后者分别对GCC和Clang产生以下错误消息:

// (Ex1)

// GCC
error: no matching function for call to 'func(Test<int, double>)'
note:   template argument deduction/substitution failed:
        deduced conflicting types for parameter 'T' ('int' and 'double')

// Clang
error: no matching function for call to 'func'
note: candidate template ignored: deduced 
      conflicting types for parameter 'T' ('int' vs. 'double')

我们最终可以注意到,如果我们调整// (Ex2) // GCC error: could not convert 'Test<Foo>{}' from 'Test<Foo>' to 'Test<int>' // Clang error: no matching function for call to 'f' note: candidate function template not viable: no known conversion from 'Test<Foo>' to 'Test<int>' for 1st argument 来明确指定(Ex1)单个模板参数,则GCC和Clang都会产生与{{1 }},暗示论点推导已从等式中完全删除。

f

此差异的关键可能是在[temp.deduct]/6 [强调我的]中指定的:

在模板自变量推导过程中的某些点上,必须采用利用模板参数的函数类型,并用相应的模板自变量替换这些模板参数。当将任何显式指定的模板参数替换为函数类型时,将在模板参数推导的开始处进行;如果在模板参数推导的末尾,则再次进行从默认参数推导或获得的值将被替换。

即模板参数推导过程分为清晰的开始 end ,分类为:

  • 明确指定模板参数作为流程的开始,并且
  • 推论或默认的自变量获得的模板自变量作为过程的结束,

这将解释上述示例的错误消息中的差异;如果在推导过程的开始已明确指定了所有模板参数,则该过程的其余部分将没有任何剩余的模板参数可用于w.r.t。推导或默认模板参数。

答案 3 :(得分:-1)

我试图提出一种代码,该代码仅强制类推导而不强制函数推导。
这里,没有函数实例化,但是编译器仍然会发出错误:

template<typename T, typename U = T>
struct Test{};

template<typename T> 
void func(Test<T, T>){
}

template<typename T>
void func(Test<T>){  
}

redefinition of 'template<class T> void func(Test<T, T>)'

海湾合作委员会:https://godbolt.org/z/7c981E
铛: https://godbolt.org/z/G1eKTx

先前的错误答案:

P指的是模板参数,而不是模板本身。在声明Test<typename T, typename U = T>中,P表示T,而不是Test。因此,在实例化Test<int>中T是int的,就像调用中的A也是int的一样。