“即时上下文"究竟是什么?SFINAE 适用的 C++11 标准中提到的?

2021-12-13 00:00:00 language-lawyer templates c++ c++11 sfinae

C++11 标准的第 14.8.2/8 段指定了在何种条件下替换失败会或不会导致硬"编译错误(从而导致编译失败)或软"错误这只会导致编译器从一组候选重载解析中丢弃模板(不会使编译失败并启用众所周知的 SFINAE 成语):

Paragraph 14.8.2/8 of the C++11 Standard specifies the conditions under which a substitution failure shall or shall not result in a "hard" compilation error (thereby causing compilation to fail) or in a "soft" error which would just cause the compiler to discard a template from a set of candidates for overload resolution (without making compilation fail and enabling the well-known SFINAE idiom):

如果替换导致无效的类型或表达式,则类型推导失败.无效的类型或表达式如果使用替换参数编写,则将是格式错误的.[注意:访问检查是这样完成的替代过程的一部分.―end note ] 仅在直接上下文中无效的类型和表达式函数类型及其模板参数类型可能导致推导失败.[...]

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [ Note: Access checking is done as part of the substitution process. ―end note ] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure. [...]

直接上下文"这个词在整个 C++11 标准中只出现了 8 次,并且每次它们后跟(或作为其一部分出现)以下(非-规范)文本:

The words "immediate context" appear only 8 times in the whole C++11 Standard, and each time they are followed by (or occur as part of) an instance of the following (non-normative) text:

[注:评价替换的类型和表达式可能会导致副作用,例如类模板的实例化专业化和/或函数模板专业化,隐式定义函数的生成等.这种副作用不在直接上下文"中,可能导致程序格式错误.―end注意]

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

The note gives a (not very generous) hint on what is meant by immediate context, but at least for me this is often not enough to decide whether a substitution is or is not supposed to cause a "hard" compilation error.

问题:

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

Could you provide an explanation, a decision procedure, and/or some concrete examples to help figuring out in what cases a substitution error does and does not occur in the "immediate context" of the function type and its template parameter types?

推荐答案

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

If you consider all the templates and implicitly-defined functions that are needed to determine the result of the template argument substitution, and imagine they are generated first, before substitution starts, then any errors occurring in that first step are not in the immediate context, and result in hard errors.

如果所有这些实例化和隐式定义(可能包括将函数定义为已删除)都可以毫无错误地完成,那么在替换过程中发生的任何进一步错误"(即在引用实例化模板和隐式定义函数时)函数模板的签名)不是错误,而是导致推导失败.

If all those instantiations and implicitly-definitions (which might include defining functions as deleted) can be done without error, then any further "errors" that occur during substitution (i.e. while referring to the instantiated templates and implicitly-defined functions in the function template's signature) are not errors, but result in deduction failures.

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

So given a function template like this:

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

以及如果其他函数的推导失败将使用的回退":

and a "fall-back" that will be used if deduction fails for the other function:

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

和这样的类模板:

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

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

A call to func<A<int&>>(nullptr) will substitute A<int&> for T and in order to check if T::type exists it must instantiate A<int&>. If we imagine putting an explicit instantiation before the call to func<A<int&>(nullptr):

template class A<int&>;

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

then that would fail, because it tries to create the type int&* and pointers to references are not allowed. We don't get to the point of checking if substitution succeeds, because there is a hard error from instantiating A<int&>.

现在假设有一个 A 的显式特化:

Now let's say there's an explicit specialization of A:

template<>
  struct A<char>
  {
  };

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

A call to func<A<char>>(nullptr) requires the instantiation of A<char>, so imagine an explicit instantiation somewhere in the program before the call:

template class A<char>;

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

This instantiation is OK, there's no error from this, so we proceed to argument substitution. The instantiation of A<char> worked, but A<char>::type doesn't exist, but that's OK because it's only referenced in the declaration of func, so only causes argument deduction to fail, and the fall-back ... function gets called instead.

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

In other situations substitution might cause special member functions to be implicitly-defined, possibly as deleted, which might trigger other instantiations or implicit definitions. If errors occur during that "generating instantiations and implicit definitions" stage then they're errors, but if that succeeds but during substitution an expression in the function template signature turns out to be invalid e.g. because it uses a member that doesn't exist or something that got implicitly defined as deleted, that's not an error, just a deduction failure.

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

So the mental model I use is that substitution needs to do a "preparation" step first to generate types and members, which might cause hard errors, but once we have all the necessary generation done, any further invalid uses are not errors. Of course all this does is move the problem from "what does immediate context mean?" to "Which types and members need to be generated before this substitution can be checked?" so it may or may not help you!

相关文章