考虑以下代码:
#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
应用于 vector 来初始化Var
的第一个元素,这是行不通的。如果未定义
BROKEN
,则g++显然足够聪明,可以意识到initializer_list重载将无法正常工作。哪个是正确的行为,还是标准都允许?
BROKEN defined gcc
BROKEN not defined gcc
BROKEN defined clang
最佳答案
这是解决此问题的两种方法:
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 clang ,但这只是一种直觉)。也许更了解标准的人可以说出来。