使用大括号初始值设定项的C ++隐式转换

时间:2016-05-27 11:40:02

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

我最近在某处(不记得在哪里)阅读使用大括号来允许多个用户定义的转换,但是构造函数转换和转换方法转换之间似乎存在差异,我不明白。

考虑:

#include <string>

using ::std::string;

struct C {
  C() {}
};

struct A {
  A(const string& s) {}  // Make std::string convertible to A.
  operator C() const { return C(); }  // Makes A convertible to C.
};

struct B {
  B() {}
  B(const A& a) {}  // Makes A convertible to B.
};

int main() {
  B b;
  C c;

  // This works.
  // Conversion chain (all thru ctors): char* -> string -> A -> B
  b = {{"char *"}};

  // These two attempts to make the final conversion through A's
  // conversion method yield compiler errors.
  c = {{"char *"}};
  c = {{{"char *"}}};
  // On the other hand, this does work (not surprisingly).
  c = A{"char *"};
}

现在,我可能会错误地解释编译器正在做什么,但是(基于上面和其他实验)我觉得它并没有考虑转换方法的转换。然而,阅读标准的第4节和第13.3.3.1节,我无法找到原因。解释是什么?

更新

这是我想解释的另一个有趣的现象。如果我添加

struct D {
  void operator<<(const B& b) {}
};

main

  D d;
  d << {{ "char *" }};

我收到错误,但如果我写d.operator<<({{ "char *" }});它就可以了。

更新2

看起来标准中的第8.5.4节可能会有一些答案。我会报告我的发现。

2 个答案:

答案 0 :(得分:5)

可以进行一次用户转换。

b = {{"char *"}};

我们实际上是

b = B{{"char*"}}; // B has constructor with A (and a copy constructor not viable here)

所以

b = B{A{"char*"}}; // One implicit conversion const char* -> std::string

c = {{"const char*"}}中,我们尝试

c = C{{"char *"}}; // but nothing to construct here.

答案 1 :(得分:0)

仔细阅读标准的第8.5.4节,并遵循其中的各种交叉引用,我想我明白了发生了什么。当然,IANAL,所以我可能错了;这是我最大的努力。

更新:以前版本的答案实际上使用了多次转化。我已更新它以反映我目前的理解。

揭开混乱的关键是braced-init-list 一个表达式(这也解释了为什么d << {{"char *"}}不会编译)。它是由特殊规则控制的特殊语法,允许在许多特定的上下文中使用。在这些上下文中,我们讨论的相关内容是:赋值的rhs,函数调用中的参数和构造函数调用中的参数。

那么当编译器看到b = {{"char *"}}时会发生什么?这是分配rhs的情况。适用的规则是:

  

braced-init-list 可能出现在...由用户定义的赋值运算符定义的赋值的右侧,在这种情况下,初始化列表将被传递   作为运算符函数的参数。

(据推测,默认的复制赋值运算符被认为是用户定义的赋值运算符。我无法在任何地方找到该术语的定义,并且似乎没有是支持默认拷贝分配的大括号语法的任何语言。)

因此我们将参数传递给默认的复制赋值运算符B::operator=(const B&),其中传递的参数为{{"char *"}}。因为braced-init-list不是表达式,所以这里没有转换问题,而是B类型的临时表的初始化形式,特别是所谓的列表初始化

  

如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中   候选函数是类T的所有构造函数,参数列表由元素组成   初始化列表。

因此,编译器剥离外部大括号并使用{"char *"}作为参数执行重载决策。这成功,匹配构造函数B::B(const A&),因为再次列出了A类型临时的列表初始化,其中重载决策成功匹配A::A(const string&)参数"char *",这是可以通过一个分配的用户定义转换,即从char*string

现在,在c = {{"char *"}}的情况下,过程类似,但是当我们尝试使用C列出初始化类型为{{"char *"}}的临时文件时,重载解析无法找到构造函数匹配。关键是,根据定义,list-initialization只能通过一个构造函数来工作,该构造函数的参数列表可以与列表的内容相匹配。