问题描述
我遇到了一个有趣的行为,其中模板似乎会影响嵌套的 std::initializer_list
是否不明确。考虑以下示例:
#include <initializer_list>
#include <iostream>
template <typename T = int>
void constructor_T(std::initializer_list<T> l) {
std::cout << "constructor_T 1D" << std::endl;
}
template <typename T = int>
void constructor_T(std::initializer_list<std::initializer_list<T>> ll) {
std::cout << "constructor_T 2D" << std::endl;
}
void constructor_int(std::initializer_list<int> l) {
std::cout << "constructor_int 1D" << std::endl;
}
void constructor_int(std::initializer_list<std::initializer_list<int>> ll) {
std::cout << "constructor_int 2D" << std::endl;
}
int main() {
constructor_T({}); // constructor_T 2D,why not ambiguous?
constructor_T({{},{}}); // constructor_T 2D,why not ambiguous?
constructor_T({1,2,3,4}); // constructor_T 1D
constructor_T({{1,2},{3,4}}); // constructor_T 2D
constructor_int({}); // ambiguous
constructor_int({{},{}}); // ambiguous
constructor_int({1,4}); // constructor_int 1D
constructor_int({{1,4}}); // constructor_int 2D
return 0;
}
constructor_int
几乎与 constructor_T
相同,只是 constructor_int
不是模板化的。当调用 constructor_int
且初始化器列表为空时,编译器会抱怨歧义,但是 constructor_T
工作正常。
错误消息如下所示(使用 clang 7
和 gcc 7.5
测试):
// These are expected errors,the question is why constructor_T({})
// is not ambiguous.
ambiguous.cpp:28:5: error: call to 'constructor_int' is ambiguous
constructor_int({});
^~~~~~~~~~~~~~~
ambiguous.cpp:14:6: note: candidate function
void constructor_int(std::initializer_list<int> l) {
^
ambiguous.cpp:18:6: note: candidate function
void constructor_int(std::initializer_list<std::initializer_list<int>> ll) {
解决方法
constructor_int({}); // ambiguous,why?
您可以使用 initializer_list<int>
构造 initializer_list<initializer_list<int>>
和 {}
。
constructor_int({{},{}}); // ambiguous,why?
您可以使用 initializer_list<int>
构造 initializer_list<initializer_list<int>>
和 {{},{}}
。
试试看
initializer_list<initializer_list<int>> a={{},{}};
initializer_list<int> b={{},{}}; // aka {0,0}
所以这些很无聊。两者都可以。
但为什么模板有效?
“更专业”的规则。
当两个模板都有效时,只有更专业的一个参与重载解析。
这类似于
template<class T>
void foo(T);
template<class U>
void foo(U*);
当我打电话
foo((void*)0);
我们得到 T=void*
和 U=void
template<class T=void*>
void foo(void*);
template<class U=void>
void foo(void*);
如果你忽略模板更专业的规则,两者都是同样好的重载。
但是因为 T
可以是任何 U*
而 U*
不能是任何 T
,所以 U*
更专业。
所以 C++ 选择 U*
。
同样的事情发生在 initializer_list<initializer_list<T>>
比 initializer_list<T>
更专业。