42

在 C++ 标准 §13.3.1.7 [over.match.list] 中,声明如下:

在复制列表初始化中,如果explicit选择了构造函数,则初始化格式错误。

这就是为什么我们不能这样做的原因,例如,这样的事情:

struct foo {
    // explicit because it can be called with one argument
    explicit foo(std::string s, int x = 0);
private:
    // ...
};

void f(foo x);

f({ "answer", 42 });

(请注意,这里发生的不是转换,即使构造函数是“隐式的”也不会是转换。这是foo直接使用其构造函数初始化对象。除了 之外std::string,这里没有转换。)

这对我来说似乎很好。隐式转换绝不会咬我。

如果{ "answer", 42 }可以初始化其他东西,编译器就不会背叛我做错事:

struct bar {
    // explicit because it can be called with one argument
    explicit bar(std::string s, int x = 0);
private:
    // ...
};

void f(foo x);
void f(bar x);

f({ "answer", 42 }); // error: ambiguous call

没有问题:调用不明确,代码无法编译,我必须明确选择重载。

f(bar { "answer", 42 }); // ok

由于明确规定了禁令,我觉得我在这里遗漏了一些东西。据我所知,列表初始化选择显式构造函数对我来说似乎不是问题:通过使用列表初始化语法,程序员已经表达了进行某种“转换”的愿望。

会出什么问题?我错过了什么?

4

4 回答 4

28

从概念上讲,复制列表初始化是将复合值转换为目标类型。提出措辞和解释理由的论文已经认为“复制列表初始化”中的“复制”一词是不幸的,因为它并没有真正传达其背后的实际理由。但保留它是为了与现有措辞兼容。对{10, 20}/元组值不应该能够复制初始化 a String(int size, int reserve),因为字符串不是对。

显式构造函数被考虑但禁止使用。这在以下情况下有意义

struct String {
  explicit String(int size);
  String(char const *value);
};

String s = { 0 };

0不传达字符串的值。所以这会导致错误,因为考虑了两个构造函数,但是explicit选择了一个构造函数,而不是0被视为空指针常量。

不幸的是,这也发生在跨函数的重载决议中

void print(String s);
void print(std::vector<int> numbers);

int main() { print({10}); }

由于模棱两可,这也是不正确的。在 C++11 发布之前,有些人(包括我)认为这很不幸,但没有提出对此进行更改的论文(据我所知)。

于 2012-02-06T10:26:06.933 回答
2

这个说法:

在复制列表初始化中,如果explicit选择了构造函数,则初始化格式错误。

意味着很多东西。其中,它意味着它必须查看显式构造函数。毕竟,如果它看不到它,它就无法选择显式构造函数。当它寻找将大括号列表转换成的候选时,它必须从所有候选中进行选择。甚至那些后来被发现是非法的。

如果重载决议导致多个函数同样可行,那么它会导致需要手动用户干预的模棱两可的调用。

于 2012-02-06T09:39:26.300 回答
2

是不是因为“显式”可以停止隐式转换,而您要求它进行隐式转换?

如果您使用单个参数构造函数指定了结构,您会问这个问题吗?

于 2012-02-06T08:46:02.780 回答
1

据我了解,关键字显式的目的是拒绝使用此构造函数进行隐式转换。

所以你问为什么显式构造函数不能用于隐式转换?显然是因为该构造函数的作者通过使用显式关键字明确拒绝了它。您发布的标准中的引用只是指出显式关键字也适用于初始化列表(不仅仅是某种类型的简单值)。

添加:

更准确地说:与某个构造函数一起使用的关键字explicit的目的是绝对清楚地表明该构造函数在某个地方使用(即强制所有代码显式调用该构造函数)。

而 IMO 语句(例如f({a,b})whenf是函数的名称)与显式构造函数调用无关。绝对不清楚(和上下文相关的)这里使用了哪个构造函数(以及什么类型),例如它取决于存在的函数重载。

另一方面,类似f(SomeType(a,b))的东西完全不同——很明显,我们使用了SomeType接受两个参数的类型的构造函数a,b,并且我们使用f了最好接受单个类型参数的函数重载SomeType

因此,一些构造函数可以像隐式使用一样f({a,b}),而另一些则要求它们的使用事实对读者来说是绝对清楚的,这就是我们将它们声明为显式的原因。

添加2:

我的观点是:有时明确声明构造函数是绝对有意义的,即使一切都不会出错。IMO 构造函数是否显式更多的是其逻辑问题,而不是任何类型的警告。

例如

double x = 2; // looks absolutely natural
std::complex<double> x1 = 3;  // also looks absolutely natural
std::complex<double> x2 = { 5, 1 };  // also looks absolutely natural

std::vector< std::set<std::string> >  seq1 = 7; // looks like nonsense
std::string str = some_allocator; // also looks stupid
于 2012-02-06T08:45:18.147 回答