什么时候嵌套 initializer_list 不明确,为什么模板会影响它的行为?
When is nested initializer_list ambiguous, and why do templates affect its behavior?
我遇到了一个有趣的行为,模板似乎会影响嵌套 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, 2, 3, 4}); // constructor_int 1D
constructor_int({{1, 2}, {3, 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>
更专业。
我遇到了一个有趣的行为,模板似乎会影响嵌套 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, 2, 3, 4}); // constructor_int 1D
constructor_int({{1, 2}, {3, 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>
更专业。