static_assert依赖于非类型模板参数(gcc和clang上的不同行为)

bol*_*lov 25 c++ templates dependent-name language-lawyer c++14

template <int answer> struct Hitchhiker {
  static_assert(sizeof(answer) != sizeof(answer), "Invalid answer");
};

template <> struct Hitchhiker<42> {};
Run Code Online (Sandbox Code Playgroud)

在尝试禁用常规模板实例化的同时,static_assert我发现clang即使模板未实例化,上面的代码也会生成断言错误,而gcc只有在Hitchhiker使用除了以外的参数进行实例化时才生成断言错误42.

摆弄我发现这个断言:

template <int answer> struct Hitchhiker {
  static_assert(sizeof(int[answer]) != sizeof(int[answer]), "Invalid answer");
};

template <> struct Hitchhiker<42> {};
Run Code Online (Sandbox Code Playgroud)

两个编译器的行为相同:只有在实例化通用模板时,断言才会启动.

标准说什么,哪个编译器是对的?

g++ 4.9.2
clang++ 3.50
Run Code Online (Sandbox Code Playgroud)

Bar*_*rry 15

两个编译器都是正确的.来自[temp.res]/8:

如果无法为模板生成有效的专业化,并且未实例化该模板,则模板格式错误,无需诊断.

没有可以从主模板生成的有效专业化Hitchhiker,因此它格式错误,无需诊断.clang无论如何选择发布诊断.

如果您只想允许42,那么只需不要定义通用模板:

template <int > struct Hitchhiker;
template <> struct Hitchhiker<42> {};
Run Code Online (Sandbox Code Playgroud)

  • 但模板确实有一个有效的专业化. (4认同)
  • 有一个有效的模板专业化. (3认同)
  • 您正在谈论的主要模板与明确的专业化"Hitchhiker <42>"无关. (3认同)
  • 进一步下来的另一个引用似乎是适用的:"如果由于不依赖于模板参数的构造而在其定义之后立即对模板进行假设实例化是不正确的,则该程序是不正确的;不需要诊断" (2认同)
  • @Tart一个问题是,`sizeof(回答)!= sizeof(回答)`是一个依赖于'回答'的结构(以一种琐碎,无聊的方式,但它仍然取决于它)?基本上是`false`,但是`false`显然不依赖于`answer`,而`answer!= answer`确实(语法上). (2认同)
  • 从[temp.dep]"在模板内部,一些构造具有可能因实例化而异的语义.这样的构造取决于模板参数".这里的语义与一个实例不同,因此您可以认为该构造不依赖于该参数. (2认同)
  • 然而,引用的段落不需要非依赖性表达.如果编译器可以证明模板无法实例化,则即使原因是依赖表达式或类型,也可以自由发出诊断.显然,由于哥德尔的不完备性定理,不能要求诊断.在模板中有"static_assert(false,...)"有更复杂的解决方法,不应该实例化(通过创建一个依赖表达式),但是虽然它们安抚了clang,但它们不足以使程序格式良好. (2认同)

Yak*_*ont 14

@TartainLlama发现的行情

如果由于不依赖于模板参数的构造而导致紧接其定义之后的模板的假设实例化是不正确的,则该程序是不正确的; 无需诊断.

N4296 [temp.res]/8

这在定义主模板后立即应用(包含其中的模板static_assert).因此,后来的专业化(for 42)不能被考虑,因为它还不存在.

接下来的问题是,如果static_assert( sizeof(answer) != sizeof(answer), 依赖answer.从语义上讲,它没有,从语法上来说,它是标准的:

在模板内部,一些构造具有可能在一个实例化到另一个实例化之间不同的语义.这种结构取决于模板参数.

N4296 [temp.dep]/1

该构造sizeof(answer) != sizeof(answer)与一个实例不同.所以这样的构造不依赖于模板参数.这意味着整个static_assert不依赖于模板参数.

因此,您的程序形成不良,无需诊断.发出任意诊断(例如static_assert失败)是有效的编译器行为.缺少问题是有效的编译器行为.从标准形成的,没有诊断要求的程序编译的程序的行为不是由标准定义的:它是未定义的行为.允许鼻腔恶魔.

花哨的尝试(sizeof(int[answer])!=sizeof(int[answer])可能会喜欢当前的编译器,但不会使你的程序更好地形成.

你可以提出一个编译器不太可能抓住你的情况,但是不管编译器是否能够抓住你,它仍然存在形式错误.作为一般规则,C++希望让自己(及其编译器)自由地"在实例化之前"找到无效的模板代码; 这意味着模板代码必须生成可能合法的代码.

您可能需要=delete附加消息的内容.