【问题标题】:How does substitution work in template argument deduction?模板参数推导中的替换如何工作?
【发布时间】:2026-01-10 12:50:01
【问题描述】:

C++ 标准 14.8.2$7 说:

替换发生在函数类型和模板参数声明中使用的所有类型和表达式中。表达式不仅包括常量表达式,例如出现在数组边界或作为非类型模板参数的常量表达式,还包括sizeofdecltype 和其他允许非常量表达式的上下文中的通用表达式(即非常量表达式) .替换按词汇顺序进行,并在遇到导致演绎失败的条件时停止。 [注意:异常规范中的等效替换仅在异常规范被实例化时进行,此时如果替换导致无效的类型或表达式,则程序是非良构的。 ——尾注]

该标准在这里提供了一个示例:

template <class T> struct A { using X = typename T::X; };
template <class T> typename T::X f(typename A<T>::X);
template <class T> void f(...) { }
template <class T> auto g(typename A<T>::X) -> typename T::X;
template <class T> void g(...) { }

void h() {
  f<int>(0); // OK, substituting return type causes deduction to fail
  g<int>(0); // error, substituting parameter type instantiates A<int>
}

为什么在这里调用g&lt;int&gt;(0) 会出错?尾随返回类型T::X 不会导致替换失败吗?模板函数fg有什么区别?

【问题讨论】:

    标签: c++ substitution


    【解决方案1】:

    重点是,首先,

    替换按词法顺序进行,并在出现条件时停止 遇到导致扣减失败的情况

    其次,A&lt;int&gt; 定义的实例化会触发硬错误,而不是替换失败,因为这会导致在直接上下文之外实例化格式错误的构造 typename T::X(带有T == int)。 [temp.deduct]/8:

    只有在直接上下文中的无效类型和表达式 函数类型及其模板参数类型可以导致 扣减失败。 [ 注意:替换类型的评估 和表达式可能会导致副作用,例如实例化 类模板特化和/或函数模板 专业化,隐式定义函数的生成等。 这种副作用不在“直接背景”中,可能导致 程序格式不正确。 — 尾注 ]

    对于有争议的模板,将函数类型中的typename T::X 代入会导致扣除失败(即SFINAE);替换为typename A&lt;T&gt;::X 会导致硬错误。由于替换是按词法顺序进行的,对于template &lt;class T&gt; typename T::X f(typename A&lt;T&gt;::X);,它首先替换为typename T::X,导致演绎失败,并且不再尝试进一步替换。另一方面,对于template &lt;class T&gt; auto g(typename A&lt;T&gt;::X) -&gt; typename T::X;,它首先替换为typename A&lt;T&gt;::X,这会导致硬错误。

    【讨论】:

    • 返回类型如何算作函数签名的一部分而不是参数?
    • @Barry 我认为返回类型和参数都被视为函数签名的一部分
    • @Barry 它们都是函数模板签名的一部分,但我可能不应该在这里使用“签名”。
    • @T.C.我想更具体的问题 - 为什么返回类型算作直接上下文而不是参数类型?
    • @Barry 与返回类型/参数类型无关。 A&lt;T&gt;::X 格式错误不是因为X 不是A&lt;T&gt; 的成员(这在直接上下文中会是一个错误),而是因为A&lt;T&gt; 的定义本身格式错误。