当前位置: 首页 > 知识库问答 >
问题:

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

卫烨
2023-03-14

C 11标准第14.8.2/8段规定了替换失败会或不会导致“硬”编译错误(从而导致编译失败)或“软”错误的条件,该错误只会导致编译器丢弃一组候选模板以进行过载解决(而不会使编译失败并启用著名的SFINAE习语):

如果替换导致无效的类型或表达式,则类型推断将失败。无效类型或表达式是使用替换参数编写时格式不正确的类型或表达式。[注意:访问检查是替换过程的一部分。-结束注意]只有函数类型及其模板参数类型的直接上下文中的无效类型和表达式才能导致推断失败。[...]

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

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

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

问题:

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

共有2个答案

冷翼
2023-03-14

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

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

直播版。

颜志学
2023-03-14

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

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

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

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

以及在其他功能扣除失败时使用的“回退”:

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

还有这样一个类模板:

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

调用func

template class A<int&>;

那么这将失败,因为它试图创建类型int

现在让我们假设有一个明确的专门化A

template<>
  struct A<char>
  {
  };

调用func

template class A<char>;

这个实例化没有问题,没有错误,所以我们继续进行参数替换。A的实例

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

所以我使用的心智模型是,替换需要先做一个“准备”步骤来生成类型和成员,这可能会导致硬错误,但一旦我们完成了所有必要的生成,任何进一步的无效使用都不是错误。当然,所有这些都是为了把问题从“即时语境意味着什么?”至“在检查此替换之前需要生成哪些类型和成员?”所以它可能会也可能不会帮助你!

 类似资料:
  • 问题内容: 我用谷歌搜索并阅读了Java文档,但我有些困惑。有人可以用简单的英语解释吗? 问题答案: 用编程的术语来说,它是较大的周围部分,可以对当前工作单元的行为产生 任何 影响。例如,使用的运行环境,环境变量,实例变量,局部变量,其他类的状态,当前环境的状态等。 在某些API中,您会在接口/类中看到此名称,例如Servlet ,JSF ,Spring ,Android ,JNDI 等。它们通常

  • 问题内容: 我的头衔几乎概括了这一切。 谁能启发我… “ JavaScript中的“执行上下文”是什么?” 以及它与“ this”,吊装,原型链,作用域和垃圾收集之间的关系? 问题答案: 您要问的是几个密切相关的不同概念。我将尝试简要地解决每个问题。 执行上下文 是语言规范中的一个概念,用外行的术语来说,大致等同于函数执行的“环境”。也就是说,变量作用域(以及 作用域链 ,外部作用域的闭包中的变量

  • 标记接口是一个没有任何成员的接口。可序列化就是一个例子。 我们可以定义自己的标记界面吗。如果是,我们如何定义它的功能。?

  • 问题内容: Java中的引用到底是什么?它是一个内存地址吗?Java引用等同于取消引用的C ++指针吗? 换句话说,给出以下内容: 上面的比较是否等同于比较C ++中的两个指针? 问题答案: 相当于在C / C ++中比较两个指针,是的。 但是,Java中的引用和C / C ++中的指针之间有两个非常重要的主要区别: Java引用无法执行指针算术:不能将3引用“加”, 只能 使其指向另一个(已知)

  • 内存中存放的数据在计算机关机后就会消失。要长久保存数据,就要使用硬盘、光盘、U 盘等设备。为了便于数据的管理和检索,引入了“文件”的概念。 一篇文章、一段视频、一个可执行程序,都可以被保存为一个文件,并赋予一个文件名。操作系统以文件为单位管理磁盘中的数据。 成千上万个文件如果不加分类放在一起,用户使用起来显然非常不便,因此又引入了树形目录(目录也叫 文件夹)的机制,可以把文件放在不同的文件夹中,文