C ++ std :: vector initializer_list重载歧义(g ++ / clang ++)

时间:2019-03-11 10:27:24

标签: c++ stdvector initializer-list uniform-initialization

考虑以下代码:

#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)都会无错误地进行编译,但是如果定义了BROKEN,则g ++(8.2.1)会引发错误:

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}视为初始化列表,并尝试将v应用于向量,从而初始化Var的第一个元素,这将不起作用

如果未定义BROKEN,则g ++显然足够聪明,以至于意识到initializer_list重载将无法正常工作。

哪种行为是正确的,还是标准允许的?

BROKEN defined gcc
BROKEN not defined gcc
BROKEN defined clang

1 个答案:

答案 0 :(得分:2)

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

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;
};

Live demo

或者:

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

    std::vector<Var> v;
};

Live demo

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

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

并且当使用括号代替括号来初始化v初始化列表时,

是不可取的。

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