6

这个程序合法吗?

struct X { X(const X &); };
struct Y { operator X() const; };

int main() {
  X{Y{}};   // ?? error
}

n2672之后,并由缺陷 978修改,13.3.3.1 [over.best.ics]具有:

4 - 但是,当考虑构造函数或用户定义的转换函数的参数时,它是 13.3.1.7 [...] 的候选 [...] 当初始化器列表只有一个元素并且转换到某个类 X或对(可能是 cv 限定的)X 的引用被认为是 X [...] 的构造函数的第一个参数,仅考虑标准转换序列和省略号转换序列。

这似乎很反常。结果是使用列表初始化强制转换指定转换是非法的:

void f(X);
f(Y{});     // OK
f(X{Y{}});  // ?? error

据我了解n2640,列表初始化应该能够替换直接初始化和复制初始化的所有用途,但似乎没有办法XY仅使用列表初始化的类型对象构造类型对象:

X x1(Y{});  // OK
X x2 = Y{}; // OK
X x3{Y{}};  // ?? error

这是标准的实际意图吗?如果不是,它应该如何阅读或阅读?

4

2 回答 2

6

13.3.3.1p4 的初衷是描述如何应用 12.3p4 中的要求:

4 - 最多一个用户定义的转换(构造函数或转换函数)隐式应用于单个值。

缺陷 84之前,13.3.3.1p4几乎是纯粹的信息:

4 - 在通过用户定义转换进行初始化的上下文中(即,当考虑用户定义转换函数的参数时;参见 13.3.1.4 [over.match.copy]、13.3.1.5 [over.match.conv] ),只允许标准转换序列和省略号转换序列。

这是因为 13.3.1.4 第 1 段第 2 条和 13.3.1.5p1b1 将候选函数限制为S产生类型的类上的函数T,其中S是初始化表达式的类类型,T是被初始化对象的类型,因此没有纬度用于插入另一个用户定义的转换序列。(13.3.1.4p1b1 是另一回事;见下文)。

缺陷 84 修复了auto_ptr漏洞(即auto_ptr<Derived> -> auto_ptr<Base> -> auto_ptr_ref<Base> -> auto_ptr<Base>通过两个转换函数和一个转换构造函数),通过限制类复制初始化的第二步中构造函数的单个参数允许的转换顺序(这里取的构造函数auto_ptr<Base>auto_ptr_ref<Base>不允许使用一个转换函数来转换它的参数auto_ptr<Base>):

4 - 但是,当考虑作为 13.3.1.3 [over.match.ctor] 候选的用户定义转换函数的参数时,当在类复制初始化的第二步中调用以复制临时文件时,或通过 13.3.1.4 [over.match.copy]、13.3.1.5 [over.match.conv] 或 13.3.1.6 [over.match.ref] 在所有情况下,只允许标准转换序列和省略号转换序列。

n2672然后添加:

[...] 由 13.3.1.7 [over.match.list] 当初始化器列表作为单个参数传递或初始化器列表只有一个元素并且转换为某个类 X 或引用(可能是 cv 限定)时X 被认为是 X 的构造函数的第一个参数,[...]

这显然令人困惑,因为 13.3.1.3 和 13.3.1.7 候选的唯一转换是构造函数,而不是转换函数。 缺陷 978纠正了这一点:

4 - 但是,当考虑构造函数或用户定义的转换函数的参数时 [...]

这也使得 13.3.1.4p1b1 与 12.3p4 一致,否则它将允许在复制初始化中无限应用转换构造函数:

struct S { S(int); };
struct T { T(S); };
void f(T);
f(0);   // copy-construct T by (convert int to S); error by 12.3p4

那么问题是指 13.3.1.7 的语言是什么意思。 X正在复制或移动构造,因此该语言不包括应用用户定义的转换来达到其X参数。 std::initializer_list没有转换功能,因此该语言必须旨在应用于其他事物;如果不打算排除转换函数,则必须排除转换构造函数:

struct R {};
struct S { S(R); };
struct T { T(const T &); T(S); };
void f(T);
void g(R r) {
    f({r});
}

列表初始化有两个可用的构造函数;T::T(const T &)T::T(S)。通过排除复制构造函数(因为它的参数需要通过用户定义的转换序列进行转换),我们确保只T::T(S)考虑正确的构造函数。在没有这种语言的情况下,列表初始化将是模棱两可的。将初始值设定项列表作为单个参数传递的工作方式类似:

struct U { U(std::initializer_list<int>); };
struct V { V(const V &); V(U); };
void h(V);
h({{1, 2, 3}});

编辑:经历了所有这些之后,我发现Johannes Schaub的讨论证实了这一分析:

这旨在为列表初始化排除复制构造函数,因为因为我们被允许使用嵌套的用户定义转换,所以我们总是可以通过首先调用复制构造函数然后像我们对另一个执行相同的操作来生成一个模棱两可的第二个转换路径转换。


好的,关闭提交缺陷报告。我将提议拆分 13.3.3.1p4:

4 - 但是,当考虑作为候选的构造函数或用户定义转换函数的参数时:

  • 由 13.3.1.3 [over.match.ctor] 在类复制初始化的第二步中调用以复制临时文件时,或
  • 在所有情况下,通过 13.3.1.4 [over.match.copy]、13.3.1.5 [over.match.conv] 或 13.3.1.6 [over.match.ref],

只考虑标准转换序列和省略号转换序列;当考虑作为X13.3.1.7 [over.match.list] 候选的类的构造函数的第一个参数时,当将初始值设定项列表作为单个参数传递或初始值设定项列表只有一个元素时,用户定义的转换仅当其用户定义的转换由转换函数指定时才考虑toX或 reference to (可能是cv限定的) 。X[注意:因为在列表初始化的上下文中,隐式转换序列中允许有多个用户定义的转换,所以这个限制是必要的,以确保X使用单个参数调用的转换构造函数athat 不是类型X或派生自 的类型,对于使用本身构造的临时对象调用X的构造函数并不模棱两可。——尾注]XXa

于 2012-10-04T17:40:37.733 回答
3

XCode 4.4 附带的 clang 3.1 版本同意您的解释并拒绝X{Y{}};。和我一样,在重新阅读了标准的相关部分几次之后,FWIW。

如果我修改X的构造函数以采用两个参数,两者都是 type const X&,则 clang 接受 statement Y y; X{y,y}。(如果我尝试X{Y{},Y{}}...它会崩溃)。这似乎与 13.3.3.1p4 一致,后者要求仅针对单元素情况跳过用户定义的转换。

似乎最初仅在已经发生另一个用户定义的转换的情况下才添加对标准和省略号转换序列的限制。或者至少这就是我阅读http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#84的方式。

有趣的是,标准如何小心地将限制应用于复制初始化的第二步,该步骤从已经具有正确类型的临时复制(并且可能通过用户定义的转换获得!)。然而对于列表初始化,似乎不存在类似的机制......

于 2012-10-01T19:08:19.893 回答