【问题标题】:What exactly is the "immediate context" mentioned in the C++11 Standard for which SFINAE applies?SFINAE 适用的 C++11 标准中提到的“直接上下文”到底是什么?
【发布时间】:2013-02-22 00:45:01
【问题描述】:

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

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

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

[注:评价 替换的类型和表达式可能会导致副作用,例如类模板的实例化 特化和/或函数模板特化,隐式定义函数的生成等。 此类副作用不在“直接上下文”中,可能导致程序格式错误。—结束 注意]

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

问题:

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

【问题讨论】:

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


【解决方案1】:

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

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

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

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

还有一个“后备”,如果其他功能的扣除失败,将使用该“备用”:

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

还有一个像这样的类模板:

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

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

template class A<int&>;

那么这将失败,因为它试图创建类型 int&amp;* 并且不允许指向引用的指针。我们没有到检查替换是否成功的地步,因为实例化A&lt;int&amp;&gt; 时出现硬错误。

现在假设A 有一个明确的特化:

template<>
  struct A<char>
  {
  };

func&lt;A&lt;char&gt;&gt;(nullptr) 的调用需要A&lt;char&gt; 的实例化,因此想象一下调用之前程序中某处的显式实例化:

template class A<char>;

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

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

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

【讨论】:

  • 感谢您非常详尽和详细的解释
  • 它绝不是权威,它只是我的心智模型,它一直在不断发展,当它失败时需要定期修改!
  • 我确实觉得它很有帮助,只要它丰富了我的观点,它是否不规范也没关系
  • @AndyProwl:看到这个有点相关的话题:SFINAE, deduction vs. instantiation
  • @Nawaz:谢谢 :) 我找到了 Q&A,但我不确定“直接上下文”的正式含义是什么。我仍然认为这应该在标准中更正式地定义,但乔纳森的回答给出了很好的解释
【解决方案2】:

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

#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.

【讨论】:

  • 我将尝试通过一个示例更好地阐明我的问题。为什么this 是软错误?为什么在实例化T2&lt;int&gt;时,即使错误是在嵌套上下文中引起的,它也被认为是在“即时上下文”中?
  • @Andy:因为it isn't。 ;) ... 也将接受零参数,这与其他重载不同。
  • 模板别名也被认为是“直接上下文”。标准中尚不清楚,但这是委员会成员的意图。见groups.google.com/group/comp.std.c++/msg/c075b74ae0b052f2
  • @sellibitze:是的,正确,因为使用别名基本上是模板级宏(至少我是这么认为的)。
  • @ustulation: 不,如果你省略了&lt;int&gt; 部分,你就不能再调用f 的第一个重载——注意T 不是推导出来的,它 必须明确提供。因此,显然选择了可变参数重载。
猜你喜欢
  • 2013-05-29
  • 2011-05-26
  • 2011-07-21
  • 2017-07-07
  • 2012-03-12
  • 2011-04-24
  • 1970-01-01
  • 2015-05-29
相关资源
最近更新 更多