隐式对的模板参数推导

αλε*_*λυτ 6 c++ template-argument-deduction

考虑以下代码:

#include <utility>
#include <initializer_list>

template <typename T>
struct S {
    S(std::initializer_list<std::pair<T, int>>) {};
};

int main()
{
    S s1 {{42, 42}};          // failed due to implicit `std::pair` from `{42, 42}`
    S s2 {std::pair{42, 42}}; // ok
}
Run Code Online (Sandbox Code Playgroud)

s1由于std::pair从大括号初始值设定项列表隐式创建,实例无法编译。

有没有一种方法(可能通过用户定义的类型推导指南)s1无需在声明中指定其他类型即可进行编译?

Qui*_*mby 4

可悲的是没有。

\n

原因是模板参数无法通过嵌套大括号 ( std::initializer_list) 推导,因为它们出现在非推导上下文中中。有关此行为的更多示例,请参阅链接。

\n

为了不将 CTAD 拖入其中,您的示例相当于:

\n
#include <utility>\n#include <initializer_list>\n\ntemplate <typename T>\nvoid S(std::initializer_list<std::pair<T, int>>) {};\n\n\nint main()\n{\n    S({{42, 42}});          \n    S ({std::pair{42, 42}}); \n}\n
Run Code Online (Sandbox Code Playgroud)\n

现在让我们看看这些示例失败的原因到底是什么。标准关于参数推导的规定如下:(强调我的)

\n
\n

模板参数推导是通过将包含参与模板参数推导的模板参数的每个函数模板参数类型(称为 P)与调用的相应参数类型(称为 A)进行比较来完成的,如下所述。如果从 P 中删除引用和 cv 限定符给出 std::initializer_list<P\'> 或 P\'[N] 对于某些 P\' 和 N 并且参数是非空初始值设定项列表 ([dcl.init.list ]),然后对初始化列表的每个元素进行推导,将 P\' 作为函数模板参数类型,将初始化元素作为其参数,在 P\'[N] 情况下,如果 N 是非-type 模板参数,N 是从初始化列表的长度推导出来的。否则,初始化列表参数会导致参数被视为非推导上下文[temp.deduct.call]

\n
\n

所以,因为S接受初始值设定项列表,所以编译器首先尝试从列表中的每个参数推断其内部类型,并且它们必须匹配。这意味着这个辅助函数是为了推导的目的而创建的:

\n
template <typename X> void foo(X element);\n
Run Code Online (Sandbox Code Playgroud)\n

并使用内部列表元素进行调用,{42,42}在我们的例子中,导致foo({42,42}). 问题就在这里,你无法X从中推断出,关键是甚至没有任何关于 的信息std::pair,所以这个任务根本不可能,并且被标准明确禁止作为非推导上下文:

\n
\n

非推导的上下文是:

\n

...

\n

5.6 关联参数是初始值设定项列表 ([dcl.init.list]) 但该参数没有指定从初始值设定项列表推导的类型 ([temp.deduct.call]) 的函数参数。[ 例子:

\n

模板无效 g(T); g({1,2,3}); // 错误:没有为 T 推导出参数

\n

\xe2\x80\x94 结束示例] [temp.deduct.type]

\n
\n

现在应该清楚为什么S({std::pair{42, 42}})会起作用了。因为外部列表的参数给出为std::pair<int,int>(在调用之前由 CATD 推导),所以传递给 foo 的类型就是简单的X=std::pair<int,int>。现在可以将该列表std::initializer_list<std::pair<int,int>>与函数声明进行匹配来推导T=int,从而导致调用成功。\n所有内部元素都尝试推导X如果至少有一个成功,则所有内部元素都会尝试独立推断,那些未成功的元素必须至少是隐式可转换的。如果更多的人成功了,那么他们一定推导出了完全相同的类型。

\n
S({{42, 42}, std::pair{1,2},{2,3}}); // Works\n// Error, two deduced types do not match.\nS({{42, 42}, std::pair{1,2},std::pair{(char)2,3}}); \n// Fine, std::pair<int,int> is deduced, others fail but can be converted to it.\nS({{42, 42}, std::pair{1,2},{(char)2,3}, {(float)2,3}}); \n
Run Code Online (Sandbox Code Playgroud)\n

另一种方法是简单地手动指定T,那么不需要任何推导,只需匹配参数即可:

\n

S<int>({{42, 42}, {1,2},{2,3}});

\n

我不太清楚为什么规则是这样的,也许存在更多模板参数存在一些问题。就我个人而言,现在看这个,我觉得可以有更详细的foo继承内部列表签名的方法,例如:

\n
template<typename T>\nvoid foo(std::pair<T,int>);\n
Run Code Online (Sandbox Code Playgroud)\n

然后通过T后面。

\n