为什么除非添加括号,否则构造函数上的模板替换会失败?
Why does template substitution fail on a constructor unless I add brackets?
我试图理解为什么除非添加括号,否则替换在以下代码片段上失败:
template<typename T>
struct A {};
template<typename T>
struct B {
B(A<T>);
};
template<typename T>
void example(A<T>, B<T>);
struct C {};
struct D {
D(C);
};
void example2(C, D);
int main(int argc, char *argv[]) {
example(A<int>{}, A<int>{}); // error
example(A<int>{}, {A<int>{}}); // ok
example2(C{}, C{}); // ok
example2(C{}, {C{}}); // ok
return 0;
}
请参阅此示例:https://godbolt.org/z/XPqHww
对于example2
,我能够隐式地将C{}
传递给D
的构造函数,而不会出现任何错误。对于example
,在我添加括号之前,我不允许隐式传递A<int>{}
。
什么定义了这种行为?
example
是一个函数模板,函数参数取决于模板参数。因此,由于您没有在调用example(A<int>{}, A<int>{});
中显式指定任何模板参数,因此当您调用此函数时,将执行模板参数推导以确定调用T
应该是什么类型。
除了少数例外,模板参数推导要求可以找到一个T
,以便函数调用中的参数类型与函数参数中的类型完全匹配。
您的调用的问题在于,A<int>
显然与任何T
B<T>
完全匹配(并且也没有例外适用),因此调用将失败。
此行为是必需的,因为编译器将需要测试所有可能的类型T
以检查是否可以调用该函数。这在计算上是不可行的或不可能的。
在调用example2(C{}, C{});
中不涉及模板,因此不执行模板参数推导。由于编译器不再需要确定参数中的目标类型,因此可以考虑从已知参数类型到已知参数类型的隐式转换。一种这样的隐式转换是通过非显式构造函数D(C);
从C
构造D
。因此,调用通过该转换成功。
example2(C{}, {C{}});
实际上也是如此。
那么问题来了,example(A<int>{}, {A<int>{}});
为什么有效。这是因为可以在 C++17 标准(草案 N4659)的 [temp.deduct.type]/5.6 中找到特定规则。它表示一个函数参数/参数对,其参数是初始值设定项列表(即{A<int>{}}
),并且参数不是std::initializer_list
或数组类型的专用化(这里都不是),函数参数是非推导上下文。
非推导上下文意味着在模板参数推导过程中不会使用函数参数/参数对来计算T
的类型。这意味着其类型不需要完全匹配。相反,如果模板参数推导成功,则生成的T
将简单地替换到非推导上下文中,并且从那里将像以前一样考虑隐式转换。B<T>
可以从{A<int>}
构造T = int
,因为非显式构造函数B(A<T>);
。
现在的问题是模板参数推演是否会成功并推导出T = int
。只有当可以从另一个函数参数推导出T
时,它才能成功。事实上,仍然有第一个参数,其类型完全匹配:A<int>
/A<T>
匹配T = int
,并且由于此函数参数不使用初始值设定项列表,因此它是从中推断T
的上下文。
因此,确实对于example(A<int>{}, {A<int>{}});
从第一个参数中推导出将产生T = int
并替换到第二个参数B<T>
这使得初始化/转换B<T>{A<int>{}}
成功,因此调用是可行的。
如果你像example({A<int>{}}, {A<int>{}});
那样对两个参数使用初始值设定项列表,两个参数/参数对都将成为非推导上下文,并且不会有任何内容可以从中推断T
,因此调用将因无法推导T
而失败。
您可以通过显式指定T
来使所有调用正常工作,这样模板参数推断就变得不必要了,例如:
example<int>(A<int>{}, A<int>{});
- 模板参数替换失败,并且未完成隐式转换
- 要求子句中不允许哪些替换失败?
- 模板参数推导/替换失败,lambda作为函数指针
- 类模板参数推导失败会导致替换失败
- C++ 带有 decltype 的 SFINAE:替换失败成为错误?
- 折叠表达式模板参数推导/替换失败
- 使用"std::function"和先前推断的模板参数替换失败 - 为什么?
- 模板参数推导/替换失败 C++
- 当Boost ::绑定模板函数时,模板参数扣除/替换失败
- SFINAE使用演绎,但用替换失败
- 为什么代码中的模板参数推导/替换失败?-.
- GCC 模板参数推断/替换失败
- 为什么模板参数推导/替换失败
- 模板模板参数的替换失败
- C++模板参数推导/替换失败
- C++模板参数推导/替换失败:
- 使用类型名参数时,模板参数推导/替换失败
- 有没有办法将模板的替换失败转换为布尔值(真/假)或标签(标准::true_type/标准::false_type)
- 模板参数扣除/替换失败-STD :: find()
- std::tuple_cat替换失败