我最近在某处(不记得在哪里)阅读使用大括号来允许多个用户定义的转换,但是构造函数转换和转换方法转换之间似乎存在差异,我不明白。
考虑:
#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节可能会有一些答案。我会报告我的发现。
答案 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只能通过一个构造函数来工作,该构造函数的参数列表可以与列表的内容相匹配。