C++ 标准的哪一部分阻止显式指定此模板的参数?

jac*_*bsa 15 c++ language-lawyer variadic-templates c++17 c++20

在我的C ++旅行我遇到下面的习惯(例如这里的绕绳下降),确保一个模板函数不能有模板参数显式指定的,所以它们并非保证API的一部分,并可以自由更改,恕不打破任何人:

template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T&);
Run Code Online (Sandbox Code Playgroud)

它似乎确实有效:

foo.cc:5:3: error: no matching function for call to 'AcceptSomeReference'
  AcceptSomeReference<char>('a');
  ^~~~~~~~~~~~~~~~~~~~~~~~~
foo.cc:2:6: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'ExplicitArgumentBarrier'
Run Code Online (Sandbox Code Playgroud)

我在直观的层面上理解为什么会这样,但我想知道如何使它精确。标准的哪一部分保证无法为该模板显式指定模板参数?

我对 clang 在这里出色的错误信息感到惊讶;就像它识别成语一样。

2b-*_*b-t 8

构造形式的主要原因

 template <int&... ExplicitArgumentBarrier, typename T>
 void AcceptSomeReference(T const&);
Run Code Online (Sandbox Code Playgroud)

禁止您T显式指定模板参数,对于遵循模板参数包的所有模板参数,编译器必须能够从函数参数中推导出相应的参数(或者它们必须具有默认参数)[temp.param] /14 :

一个函数模板的模板参数包后面不能跟另一个模板参数,除非该模板参数可以从函数模板的参数类型列表([dcl.fct])中推导出来或者有一个默认参数([temp.fct])。扣除])。没有默认参数的演绎指南模板([temp.deduct.guide])的模板参数应可从演绎指南模板的参数类型列表中演绎出来。

正如@DavisHerring 所指出的,仅此一段并不一定意味着必须扣除。但是: 寻找匹配的模板参数分几个步骤执行:首先将考虑显式指定的模板参数列表[temp.deduct.general]/2,稍后才会执行模板类型推导,最后考虑默认参数[ temp.deduct.general]/5[temp.arg.explicit]/7状态在这种情况下

注3:如果明确指定,模板参数不参与模板参数推导

这意味着模板参数是否可以推导不仅取决于函数模板声明,还取决于应用模板参数推导之前的步骤,例如考虑明确指定的模板参数列表。不能显式指定模板参数包后面的模板参数,因为它不会参与模板参数推导([temp.arg.explicit]/7),因此也不会被推导(这将违反 [temp.arg.explicit]/7)。参数]/14)。

因此,当显式指定模板参数时,编译器会将它们“贪婪地”匹配到第一个参数包(至于以下参数,默认值应该可用,或者它们应该可以从函数参数中扣除!反直觉地,即使是这种情况,如果模板参数是类型和非类型参数!)。所以没有办法完全明确地指定带有签名的函数的模板参数列表

template <typename... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T&);
Run Code Online (Sandbox Code Playgroud)

如果它被调用

AcceptSomeReference<int,void,int>(8.0);
Run Code Online (Sandbox Code Playgroud)

所有类型都将归因于模板参数包而不是T. 所以在上面的例子中ExplicitArgumentBarrier = {int,void,int},并T会从参数中扣除8.0double


更糟糕的是,您可以使用引用作为模板参数

template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T&);
Run Code Online (Sandbox Code Playgroud)

为这样的屏障明确指定模板参数并非不可能,但引用模板参数非常严格,而且不太可能偶然发生,因为它们必须尊重[temp.arg.nontype]/2constexpr对于非类型)和[temp.arg.nontype]/3(对引用的限制甚至更多!):您需要某种static带有正确cv- 限定符变量(例如,类似static constexpr int xstatic int const x在以下示例中的内容也不起作用!)如下所示代码片段(在此处尝试!):

template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T& t) {
  std::cout << t << std::endl;
  return;
}

static int x = 93;
int main() {
  AcceptSomeReference<x>(29.1);
  return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

这样,通过添加这个不太可能的模板参数的可变参数模板作为第一个模板参数,您可以阻止某人指定任何模板参数。无论用户尝试放置什么,都很可能无法编译。如果没有显式模板参数列表,ExplicitArgumentBarrier将自动扣除长度为零[temp.arg.explicit]/4/Note1


对@DavisHerring 的补充评论

允许某人在可变参数模板后明确指定模板参数会导致歧义并破坏现有规则。考虑以下函数:

template <typename... Ts, typename U>
void func(U u);
Run Code Online (Sandbox Code Playgroud)

调用中的模板参数func<double>(8.0)是什么?Ts = {}, U = double或者Ts = {double}, U = double?解决这种歧义的唯一方法是允许用户仅显式指定所有模板参数(而不仅仅是一些)。但这又导致了默认参数的问题:

template <typename... Ts, typename U, typename V = int>
void func2(U u);
Run Code Online (Sandbox Code Playgroud)

对 的调用func2<double,double>(8.0)再次模棱两可(Ts = {}, U = double, V = doubleTs = {double}, U = double, V = int)。现在您必须在此上下文中禁止默认模板参数以消除这种歧义!