如果您考虑确定模板参数替换结果所需的所有模板和隐式定义的函数,并想象它们是在替换开始之前首先生成的,那么第一步中发生的任何错误都不在直接上下文中,并导致硬错误。
如果所有这些实例化和隐式定义(可能包括将函数定义为已删除)都可以正确完成,那么在替换期间发生的任何进一步“错误”(即在引用函数模板中的实例化模板和隐式定义函数时)签名)不是错误,但会导致扣减失败。
所以给定一个这样的函数模板:
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
,所以只会导致参数推导失败,...
而是调用后备函数。
在其他情况下,替换可能会导致特殊成员函数被隐式定义,可能被删除,这可能会触发其他实例化或隐式定义。如果在“生成实例化和隐式定义”阶段发生错误,那么它们就是错误,但如果成功但在替换过程中,函数模板签名中的表达式被证明是无效的,例如因为它使用了不存在的成员或被隐式定义为删除的东西,这不是错误,只是演绎失败。
所以我使用的心智模型是替换需要首先做一个“准备”步骤来生成类型和成员,这可能会导致硬错误,但是一旦我们完成了所有必要的生成,任何进一步的无效使用都不是错误。当然,这一切只是将问题从“直接上下文是什么意思?”转移开来。到“在检查此替换之前需要生成哪些类型和成员?” 所以它可能会也可能不会帮助你!