模板、良好成型性和零包装长度规则

max*_*x66 5 c++ well-formed language-lawyer variadic-templates c++11

从上一个问题的接受答案中,我发现了一条我不知道的关于模板和格式良好的规则

如果出现以下情况,则程序格式错误,无需诊断:

  • [...]
  • 可变参数模板的每个有效特化都需要一个空模板参数包,或者
  • [...]

根据这条规则(如果我理解正确的话),以下模板函数的格式不正确

template <typename ... Ts>
int foo (std::tuple<Ts...> const &)
 { return std::get<sizeof...(Ts)>(std::tuple<int>{42}); }
Run Code Online (Sandbox Code Playgroud)

因为唯一有效的专业化需要和空Ts...参数包。

但是(也许是因为我不太懂英语)在模板具有两个以上参数包的情况下,我不确定是否理解这条规则。

我的意思是......以下foo()功能

#include <tuple>
#include <iostream>

template <typename ... Ts, typename ... Us>
int foo (std::tuple<Ts...> const &, std::tuple<Us...> const &)
 { return std::get<sizeof...(Ts)+sizeof...(Us)-1U>(std::tuple<int>{42}); }

int main ()
 {
   auto t0 = std::tuple<>{};
   auto t1 = std::tuple<int>{0};

   //std::cout << foo(t0, t0) << std::endl; // compilation error
   std::cout << foo(t0, t1) << std::endl;   // print 42
   std::cout << foo(t1, t0) << std::endl;   // print 42
   //std::cout << foo(t1, t1) << std::endl; // compilation error
 }
Run Code Online (Sandbox Code Playgroud)

是格式良好还是格式错误?

因为它的有效特化要求Ts... or Us...为空(并且另一个参数包的大小恰好为 1)。

该规则应该被解释为,如果存在一个必须为空的空参数包,则程序是格式不正确的(因此我的示例应该格式良好,因为两个参数包都不能为空),或者在以下意义上:如果在每个专业化中至少有一个空参数包,并且每个专业化中不一定相同(所以我的示例应该是格式错误的),那么格式不正确?

Bri*_*ian 1

我认为标准措辞尚未清楚地传达该规则的意图,也许永远也不会。

“无有效的专门化”规则旨在允许编译器对语法上有效但在语义上可能被视为无意义的构造执行早期诊断,而无需实例化模板。这是一个例子:

template <int x>
void foo() {
    typedef double D;
    x.~D();
}
Run Code Online (Sandbox Code Playgroud)

这在语法上没有任何问题。从语义上讲,这是试图破坏 type 的值,int就好像它是 type 的值一样double,这当然是不允许的。如果实例化foo,则允许编译器对其进行诊断,但不是必需的。看起来 Clang 给出了诊断,而 GCC 没有。

同样,有关模板参数包的规则的目的是,如果包扩展的模式在语义上是无意义的,则允许编译器尽早对其进行诊断。上述示例的一个变体说明了这一点:

template <int... x>
void foo() {
    typedef double D;
    (x.~D(), ...);
}
Run Code Online (Sandbox Code Playgroud)

x.~D()其格式正确的唯一方法是展开式中有 0 个实例;即使 1 也总是格式错误的。因此,这里的想法是,允许(但不是必需)编译器诊断格式错误的模式,即使可以通过提供导致扩展中的 0 个元素的空包来获得格式良好的特化。

(同样,GCC 对此表示同意,甚至允许您调用foo<>()。即使它没有实例化,Clang 也会对其进行诊断。)

基于此,有两个参数包时的规则应该是:

  • 如果您对它们所做的唯一事情就是 use sizeof...,那么关于每个需要空包的有效专业化的规则根本不应该适用。这不符合现行规则的“精神”。
  • 如果单独展开它们,那么规则应该是,如果存在至少一个包,并且该包必须为空才能获得有效的实例化,那么该程序就是 IFNDR。这意味着编译器可以诊断任一格式错误的模式。
  • 如果您进行嵌套扩展,即您有一个包含其中一个包的模式,则扩展它,然后它是也包含第二个包的更大表达式的一部分,并且被扩展(因此结果有 MN元素,其中 M 是第一个包中的元素数量,N 是第二个包中的元素数量),那么规则应该是,如果每个有效实例化都要求至少有一个包为空,则该程序是 IFNDR。