SFINAE适用的C ++ 11标准中提到的“直接上下文”究竟是什么?

时间:2013-03-07 00:06:31

标签: c++ templates c++11 language-lawyer sfinae

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

  

如果替换导致无效的类型或表达式,则类型推导失败。无效的类型或表达式   如果使用替换参数编写,则会形成错误的。 [注意:访问检查完成为   替代过程的一部分。 -end note] 只有当前上下文中的无效类型和表达式   函数类型及其模板参数类型可能导致演绎失败。 [...]

单词“ immediate context ”在整个C ++ 11标准中仅出现8次,并且每次出现以下(或作为其中一部分)后续实例(非-normative)text:

  

[注:评价   替换类型和表达式可能导致副作用,例如类模板的实例化   专业化和/或功能模板专业化,隐式定义函数的生成等。   这种副作用不在“直接上下文”中,并且可能导致程序格式不正确。 -end   注意]

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

问题:

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

2 个答案:

答案 0 :(得分:28)

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

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

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

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)的调用会将A<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的声明中引用,因此只会导致参数推断失败,并且改为调用back ...函数。

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

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

答案 1 :(得分:5)

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

#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);
}

Live version.