43

C++11 标准的第 14.8.2/8 段规定了替换失败应或不应导致“硬”编译错误(从而导致编译失败)或“软”错误的条件导致编译器从一组候选者中丢弃模板以进行重载解析(不会导致编译失败并启用众所周知的 SFINAE 习惯用法):

如果替换导致无效的类型或表达式,则类型推导失败。无效类型或表达式是如果使用替换参数编写的格式错误的类型或表达式。[ 注意:访问检查是替换过程的一部分。—尾注]只有在函数类型及其模板参数类型的直接上下文中的无效类型和表达式会导致推导失败。[...]

直接上下文”这个词在整个 C++11 标准中只出现了 8 次,每次它们后面都跟着(或作为一部分出现)以下(非规范)文本的实例:

[注意:替换类型和表达式的求值可能会导致副作用,例如类模板特化和/或函数模板特化的实例化,隐式定义函数的生成等。这些副作用不在“立即上下文”,并可能导致程序格式错误。——尾注]

该注释给出了关于直接上下文的含义的(不是非常慷慨的)提示,但至少对我而言,这通常不足以决定替换是否会导致“硬”编译错误。

问题:

您能否提供解释、决策过程和/或一些具体示例,以帮助确定在函数类型及其模板参数类型的“直接上下文”中发生和不发生替换错误的情况下?

4

2 回答 2

41

如果您考虑确定模板参数替换结果所需的所有模板和隐式定义的函数,并想象它们是在替换开始之前首先生成的,那么第一步中发生的任何错误都不在直接上下文中,并导致硬错误。

如果所有这些实例化和隐式定义(可能包括将函数定义为已删除)都可以正确完成,那么在替换期间发生的任何进一步“错误”(即在引用函数模板中的实例化模板和隐式定义函数时)签名)不是错误,但会导致扣减失败。

所以给定一个这样的函数模板:

template<typename T>
void
func(typename T::type* arg);

以及如果其他功能的扣减失败将使用的“后备”:

template<typename>
void
func(...);

和这样的类模板:

template<typename T>
  struct A
  {
    typedef T* type;
  };

调用func<A<int&>>(nullptr)将替代andA<int&>为了T检查是否T::type存在,它必须实例化A<int&>。如果我们想象在调用之前放置一个显式实例化func<A<int&>(nullptr)

template class A<int&>;

那么这将失败,因为它试图创建类型int&*并且不允许使用指向引用的指针。我们没有到检查替换是否成功的地步,因为实例化存在一个硬错误A<int&>

现在假设有一个明确的专业化A

template<>
  struct A<char>
  {
  };

调用func<A<char>>(nullptr)需要实例化A<char>,所以想象一下调用之前程序中某处的显式实例化:

template class A<char>;

这个实例化没问题,没有错误,所以我们继续进行参数替换。工作的实例化A<char>,但A<char>::type不存在,但这没关系,因为它只在 的声明中被引用func,所以只会导致参数推导失败,...而是调用后备函数。

在其他情况下,替换可能会导致特殊成员函数被隐式定义,可能被删除,这可能会触发其他实例化或隐式定义。如果在“生成实例化和隐式定义”阶段发生错误,那么它们就是错误,但如果成功但在替换过程中,函数模板签名中的表达式被证明是无效的,例如因为它使用了不存在的成员或被隐式定义为删除的东西,这不是错误,只是演绎失败。

所以我使用的心智模型是替换需要首先做一个“准备”步骤来生成类型和成员,这可能会导致硬错误,但是一旦我们完成了所有必要的生成,任何进一步的无效使用都不是错误。当然,这一切只是将问题从“直接上下文是什么意思?”转移开来。到“在检查此替换之前需要生成哪些类型和成员?” 所以它可能会也可能不会帮助你!

于 2013-03-07T01:04:06.283 回答
7

直接上下文基本上就是您在模板声明本身中看到的内容。除此之外的一切都是一个硬错误。硬错误示例:

#include <type_traits>

template<class T>
struct trait{ using type = typename T::type; };

template<class T, class U = typename trait<T>::type>
void f(int);
void f(...);

template<class T, class U = typename T::type>
void g(int);
void g(...);

template<class>
struct dependent_false : std::false_type{};

template<class T>
struct X{
    static_assert(dependent_false<T>(), "...");
    using type = void;
};

int main(){
    f<int>(0);
    g<X<int>>(0);
}

现场版。

于 2013-03-07T00:18:41.107 回答