首页 > 解决方案 > C++ std::vector initializer_list 重载歧义 (g++/clang++)

问题描述

考虑以下代码:

#include <vector>

#define BROKEN

class Var {
public:
#ifdef BROKEN
    template <typename T>
    Var(T x) : value(x) {}
#else
    Var(int x) : value(x) {}
#endif

    int value;
};

class Class {
public:
    Class(std::vector<Var> arg) : v{arg} {}

    std::vector<Var> v;
};

无论是否BROKEN定义,Clang++ (7.0.1) 都会编译它而不会出错,但如果定义了 g++ (8.2.1),则会引发错误BROKEN

main.cpp:9:20: error: cannot convert ‘std::vector<Var>’ to ‘int’ in initialization
  Var(T x) : value(x) {}
                    ^

据我所知,这里使用的统一初始化应该std::vector(std::vector&&)在两种情况下都选择构造函数;然而,显然,相反,g++{arg}视为初始化列表并尝试初始化vwithVar应用于向量的第一个元素,这将不起作用。

如果BROKEN未定义,g++ 显然足够聪明,可以意识到 initializer_list 重载不起作用。

哪个是正确的行为,或者两者都被标准允许?

BROKEN 已定义 gcc
BROKEN 未定义 gcc
BROKEN 已定义 clang

标签: c++stdvectorinitializer-listuniform-initialization

解决方案


这是解决该问题的两种方法:

class Var {
public:
#ifdef BROKEN
    template <typename T>
    Var(T x, typename std::enable_if<std::is_convertible<T, int>::value>::type* = nullptr) : value(x) {}
#else
    Var(int x) : value(x) {}
#endif

    int value;
};

现场演示

或者:

class Class {
public:
    Class(std::vector<Var> arg) : v(arg) {}

    std::vector<Var> v;
};

现场演示

现在显然在 gcc 的情况下尝试使用模板参数作为初始化列表。当使用大括号并定义了初始化列表时,初始化列表的优先级高于复制构造函数。

所以添加正确的enable_if解决了这个问题,因为它可以防止创建构造函数的初始化列表版本。在 C++20 中,概念将以更好的方式解决这个问题。

并且当使用括号代替大括号来初始化v初始化列表时是不可取的。

我不确定谁是对的(IMO clang,但这只是一种直觉)。也许更了解标准的人可以说出来。


推荐阅读