我创建了两个类 - 一个可以通过转换运算符转换为另一个:
struct MyClass{};
struct MyClass2{
operator MyClass() const { return MyClass{}; }
};
和专门的模板函数(
std::initializer_list<MyClass>
的专门化):
template<typename T>
void foo(std::initializer_list<T>)
{
}
template<>
void foo(std::initializer_list<MyClass>)
{
std::cout << "specialised template foo<>()";
}
当我尝试使用初始化列表混合
foo
和 MyClass
来调用 MyClass2
时:
foo({MyClass{}, MyClass2{}, MyClass2{}});
编译器反对(因为我混合了两种不同的类型):
<source>:35:8: error: no matching function for call to 'foo(<brace-enclosed initializer list>)'
35 | foo({MyClass{}, MyClass2{}, MyClass2{}});
| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:19:6: note: candidate: 'template<class T> void foo(std::initializer_list<_Tp>)'
19 | void foo(std::initializer_list<T>)
| ^~~
<source>:19:6: note: template argument deduction/substitution failed:
<source>:35:8: note: deduced conflicting types for parameter '_Tp' ('MyClass' and 'MyClass2')
35 | foo({MyClass{}, MyClass2{}, MyClass2{}});
| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
但是出于某种原因,如果我为与模板专业化中相同的类型添加非模板重载
foo
- 它就像一个魅力!
void foo(std::initializer_list<MyClass>)
{
std::cout << “overloaded foo()";
}
int main(int argc, char **argv) {
foo({MyClass{}, MyClass2{}, MyClass2{}}); // Program stdout : overloaded foo()
}
我猜当编译器查找函数时,非模板重载优先于模板。但为什么它适用于重载而不适用于模板专业化呢?这是未定义的行为吗?或者这样做是完全合法的吗?
为什么它适用于重载而不适用于模板专业化?
专业化不参与重载选择,只有主模板参与。 (选择主模板后,“使用”专业化进行定义)。
模板推导“忽略”类型转换。
{..}
没有类型,只能在很少的上下文中推断出来。由于其类型并不完全相同,所以{..}
不能推导出为std::initializer_list<T>
。
使用重载
void foo(std::initializer_list<MyClass>)
,不会发生推论,并且可能会检查我们是否可以将参数与该函数匹配,并且提供的{MyClass{}, MyClass2{}, MyClass2{}}
确实可以是std::initializer_list<MyClass>
这就是为什么在一般情况下应该优先选择重载而不是专门化。
这是未定义的行为吗?或者这样做是完全合法的吗?
不存在未定义的行为。 超载是完全合法的。