什么时候嵌套 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 7gcc 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> 更专业。