2

C++

读过没有explicit关键字和一个参数的构造函数(或对具有多个参数的 ctor 的单参数调用,其中除了一个以外的所有参数都具有默认值)可以执行一次隐式转换。给定两个类,其中Foo的 ctor 有两个 int参数,而Bar' 的 ctor 有两个 Foo参数,该语句似乎暗示Bar使用两个不合格的 s 集调用 's ctorint不应转换为Bar使用两个 ' 调用 's ctor Foo。那么,为什么下面会编译呢?

struct Foo {
    Foo(int x_, int y_) : x(x_), y(y_) {}
    int x;
    int y;
};

struct Bar {
    Bar(Foo first_, Foo second_) : first(first_), second(second_) {}
    Foo first;
    Foo second;
};

#include <iostream>

int main() {
    Bar b = { { 1, 2 }, { 3, 4 } };
    std::cout << b.first.x << ", " << b.first.y << ", ";
    std::cout << b.second.x << ", " << b.second.y << std::endl;
    // ^ Output: 1, 2, 3, 4
}
4

5 回答 5

2

该声明没有任何含义。这是关于具有一个参数的构造函数的声明,并且您已经编写了具有两个参数的构造函数。它不适用。

任何转换序列最多可以包含一个用户定义的转换(根据转换的上下文,可能允许也可能不允许explicit- 在您描述的情况下可能不允许)。这里有两个单独的转换序列:一个从初始化列表获取到Bar构造函数的第一个参数,另一个从另一个初始化列表获取到第二个参数。每个序列包含一个转换,到Foo。这就是为什么你的代码很好。

在“一个用户定义的转换”规则下你不能做的是:

struct Foo { Foo(int) {} };
struct Bar { Bar(const Foo&) {} };
struct Baz { Baz(const Bar&) {} };

Baz baz(1);

因为这将需要链中的两个用户定义的转换。但是Baz baz(Foo(1));很好,因为只需一次转换即可从您提供的参数(的实例Foo)到参数Baz可以接受的参数(的实例Bar)。Baz baz(Bar(1L))也很好,因为虽然long -> int -> Foo为了从1LBar()可以接受的参数有两种转换,但其中一种转换是内置的,而不是用户定义的。

你也不能这样做:

Bar bar = 1;

因为那是复制初始化,它首先尝试将右侧转换为Bar. 链中需要两个用户定义的转换int -> Foo -> Bar,所以不好。但是你可以这样做:

Bar bar = { 1 };

在这种情况下,它等效于Bar bar(1);,而不是Bar bar = 1;T t = { 1 };和之间通常有区别,T t(1)即构造函数不能是显式的。但它不算作初始化程序转换链的一部分。T t = { 1 };T

于 2013-12-09T22:30:06.680 回答
2

您链接的文章是根据 C++11 之前的语言版本编写的,完全忽略了与统一初始化相关的 C++11 新特性。在旧版本的语言中,用户定义的转换只能隐式地用于从单个对象进行转换。

在 C++11 中,可以使用多参数构造函数从{ ... }表单的初始化程序构造(并隐式转换)对象,其中每个连续元素{}都作为相应的构造函数参数传递。这种形式的隐式转换也可以通过explicit关键字禁用。

此 C++11 转换正是您的示例代码中发生的情况。首先,使用这个新的 C++11 特性将{ 1, 2 }初始化程序转换为类型。Foo然后通过相同的 C++11 功能将{ Foo(1,2), Foo(3, 4) }其转换为类型。Bar

您发布的代码不会被 C++11 之前的编译器接受。事实上,C++11 之前的编译器禁止{ ... }对具有用户定义的构造函数的对象使用初始化器,除非{}.

于 2013-12-09T22:43:52.837 回答
1

显然,aFoo可以从两个整数初始化:

Foo f = { 1, 2 };

需要一个构造来从 生成Bar类型的第Foo一个参数{ 1, 2 },即Foo{ 1, 2 }。需要另一种构造来生成fromBar类型的第二个参数。实际上,不涉及任何隐式转换。Foo{ 3, 4 }

也就是说,请注意不同子表达式所需的转换次数不计入一次转换:如果您有多个子表达式,每个子表达式都涉及一次转换以满足参数,那没关系。

于 2013-12-09T22:30:15.650 回答
1

给定两个类,其中 Foo 的 ctor 有两个 int 参数,而 Bar 的 ctor 有两个 Foo 参数,该语句似乎暗示使用两个不合格的 int 集调用 Bar 的 ctor 不应转换为使用两个 Foo 的对 Bar 的 ctor 的调用。那么,为什么下面会编译呢?

我不确定你是如何得到这种暗示的,但这是错误的方法:如果 Foo声明了两个 int 构造函数explicit那么代码将无法编译,因为从初始化列表到的转换Foo不起作用:

 Foo f = {1,2}; // error if Foo(int, int) is explicit

如果没有explicit,则允许此转换,并且在 的构造中只发生一次用户定义的转换b,即从初始化列表到Foo

于 2013-12-09T22:31:09.293 回答
1

这条线有点棘手:

Bar b = { { 1, 2 }, { 3, 4 } };

这是一个将尝试构建一个Bar. 它采用以下构造函数Bar

Bar(Foo first_, Foo second_);

好的,所以它确实需要构建 2 Foo,没问题,让我们看看构造函数Foo是什么:

Foo(int x_, int y_);

真他妈的对 !我们刚买了两个{ int, int }!正如@juanchopanza 所说,这是发生转换的地方。我不认为括号初始化列表到 (int, int) 是一种转换,但你可以看到如果你把explicit你的构造函数放在它是一个:

converting to ‘Foo’ from initializer list would use explicit constructor ‘Foo::Foo(int, int)’
   Bar b = { { 1, 2 }, { 3, 4 } };

正如评论中指出的那样,如果您明确使用std::initializer_list它不会编译:http: //ideone.com/5enNFU

因此,它确实使用您的构造函数转换Foo了这两个初始化器来构建两个,它确实将它传递给构造器,转换包含两个的初始化器列表:BarFoo

Bar(Foo first_, Foo second_) : first(first_), second(second_) {}

而这个构造函数只调用了 2 个拷贝构造函数,Foo因为firstsecondare 的类型Foo

于 2013-12-09T22:36:16.610 回答