【问题标题】:Is it possible to invoke a user-defined conversion function via list-initialization?是否可以通过列表初始化调用用户定义的转换函数?
【发布时间】:2012-10-01 16:59:36
【问题描述】:

这个程序合法吗?

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

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

n2672 之后,经defect 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,列表初始化应该能够替换直接初始化和复制初始化的所有用途,但似乎没有办法从类型对象构造X 类型的对象Y 仅使用列表初始化:

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

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

【问题讨论】:

  • 列表初始化应该能够取代直接初始化和复制初始化的所有用途。我们需要找到传播这种错误信息的人。 ://
  • 更令人困惑的是,在同一段落中,X x2 = Y{}; 也应该是非法的,因为它属于 13.3.1.3 部分(“当调用用于复制/移动临时类复制初始化的第二步”)。这里可能还有更多事情要做。
  • @NicolBolas 我认为类复制初始化的第一步是使用转换运算符获取(临时)X,第二步从该 X 复制。或者至少这就是我的阅读方式...
  • @MooingDuck 不过,这两种初始化方法之间的特殊差异似乎很奇怪。特别是因为如果您传递 second Y (当然并相应地修改构造函数),它会起作用。我在下面的回答包含更多细节。
  • @MooingDuck Stroustrup 的 uniform initializer ideal 是(或曾经是)“一种可以在任何地方和任何地方使用的单一语法,相同的初始化值会产生相同的初始化值 " - 这不是主张替换其他初始化语法吗?

标签: c++ c++11 implicit-conversion list-initialization


【解决方案1】:

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

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

defect 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 是初始化表达式的类类型,@987654330 @是正在初始化的对象的类型,因此没有空间可以插入另一个用户定义的转换转换序列。 (13.3.1.4p1b1 是另一回事;见下文)。

缺陷 84 通过在类复制初始化的第二步(此处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 然后添加:

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

这显然令人困惑,因为 13.3.1.3 和 13.3.1.7 候选的唯一转换是构造函数,而不是转换函数。 Defect 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 Schauba discussion 证实了这一分析:

这是为了将复制构造函数分解为列表初始化 因为由于我们被允许使用嵌套的用户定义转换,我们 通过首先调用 复制构造函数,然后像我们对另一个一样做 转化率。


好的,开始提交缺陷报告。我要提议拆分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],

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

【讨论】:

  • 我喜欢你仍然允许转换函数的方式。它使struct A { A(int); }; struct B { operator A(); operator int(); }; B b; A a{ b }; 模棱两可,而不是选择A(int) ctor(就像现在一样)。当前的行为似乎令人惊讶。
  • @JohannesSchaub-litb 我一直在试图找到标准在哪里说:“在列表初始化的上下文中,隐式转换序列中允许多个用户定义的转换”,但是我找不到。我所能找到的只是类似于:“允许用户转换将初始化列表的元素转换为构造函数参数的类型”。你能告诉我哪里说允许多次转换吗?谢谢。
  • @ecatmur 我试图理解为什么在您给出的示例(第二个代码块)中,如果 13.3.3.1/4 中的措辞没有禁止,复制构造函数将是一个选项。我在这里提出了一个新问题:stackoverflow.com/questions/51619955/…。如果您能帮助我了解所涉及的步骤,我将不胜感激。谢谢。
【解决方案2】:

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

如果我修改X 的构造函数以接受两个参数,两个参数都是const X&amp;,clang 接受语句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的。

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

【讨论】:

  • 听起来像是规范中的缺陷。最好这样报告。当X{Y{}} 不能工作时,X{Y{}, Y{}} 没有理由工作。
  • @NicolBolas 我在下面起草了一份缺陷报告:stackoverflow.com/a/12732730/567292
猜你喜欢
  • 1970-01-01
  • 2012-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-05
  • 1970-01-01
  • 2023-02-06
  • 1970-01-01
相关资源
最近更新 更多