在继续阅读本文之前,请先阅读Is there a difference in C++ between copy initialization and direct initialization?,确保您了解它的内容。
我将首先总结一下这条规则(阅读标准n3225 8.5 / 16,13.3.1.3,13.3.1.4和13.3.1.5),
1)对于直接初始化,所有构造函数都将被视为重载集,重载决策将根据重载解析规则选择最佳构造函数。
2)对于复制初始化,源类型与目标类型相同或从目标类型派生,规则与上面相同,只是只转换构造函数(没有显式的构造函数)将被视为重载集。这实际上意味着显式复制/移动构造函数不会被视为重载集。
3)对于上面(2)中未包含的复制初始化情况(源类型与目标类型不同而不是从目标类型派生),我们首先考虑可以从源类型转换到目标的用户定义转换序列类型或(当使用转换函数时)到其派生类。如果转换成功,则结果将用于直接初始化目标对象。
3.1)在这个用户定义的转换序列中,根据8.5 / 16和13.3.1.4中的规则,将考虑转换ctors(非显式ctors)和非显式转换函数。
3.2)结果prvalue将直接初始化目标对象,如(1)中列出的规则,见8.5 / 16.
好的,对于规则来说,让我们看看一些奇怪的代码,我真的不知道我的推理错误在哪里,或者只是所有编译器都错了。请帮助我,谢谢。
struct A
{
A (int) { }
A() { }
explicit A(const A&) { }
};
struct B
{
operator A() { return 2; }
//1) visual c++ and clang passes this
//gcc 4.4.3 denies this, says no viable constructor available
};
int main()
{
B b;
A a = b;
//2) oops, all compilers deny this
}
根据我的理解,对于(1),
operator A() { return 2; }
因为C ++有一个规则,函数返回被视为复制初始化,根据上面的规则,2将首先隐式转换为A,这应该没问题,因为A有一个构造函数A(int)。然后转换后的临时prvalue将用于直接初始化返回的对象,这也应该没问题,因为直接初始化可以使用显式复制构造函数。所以GCC错了。
对于(2),
A a = b;
在我的理解中,首先通过运算符A()将b隐式转换为A,然后转换后的值将用于直接初始化a,当然可以调用显式复制构造函数?因此,这应该通过编译,所有编译器都错了?
注意,对于(2),visual c ++和clang都有类似的错误, “错误,无法从B转换为A”,但如果我在A的复制构造函数中删除了显式关键字,则错误消失了。
感谢阅读。
编辑1
因为有人还没有达到我的意思,我引用了8.5 / 16中的以下标准,
否则(即剩下的 复制初始化案例), 用户定义的转换序列 可以从源类型转换为 目的地类型或(当a 转换函数用于) 其派生类别列举 如13.3.1.4所述,最好的 一个是通过超载选择的 决议(13.3)。如果转换 无法做到或含糊不清的 初始化是不正确的。该 选择的函数用。调用 初始化表达式为 参数;如果函数是a 构造函数,调用初始化一个 临时的cv不合格 目标类型的版本。该 临时是一个prvalue。的结果 电话(这是暂时的) 然后使用构造函数case) 直接初始化,根据 以上规则,即对象 的目的地 副本初始化。在某些情况下, 允许实施 消除这种固有的复制 通过构造直接初始化 中间结果直接进入 正在初始化的对象;看到 12.2,12.8。
请注意,它确实提到了在用户定义的转换后直接初始化。这意味着,根据我的理解,下面的代码应遵守我评论的规则,这是由clang,coomeau online,visual c ++确认的,但GCC 4.4.3同时失败了(1)和(2)。虽然这是一个奇怪的规则,但它遵循标准的推理。
struct A
{
A (int) { }
A() { }
explicit A(const A&) { }
};
int main()
{
A a = 2; //1)OK, first convert, then direct-initialize
A a = (A)2; //2)oops, constructor explicit, not viable here!
}
答案 0 :(得分:8)
你声明了你的拷贝构造函数explicit
(BTW,为什么?),这意味着它不能再用于隐式复制类对象了。为了使用此构造函数进行复制,您现在必须使用直接初始化语法。见12.3.1 / 2
2显式构造函数与非显式构造函数一样构造对象,但仅在显式使用直接初始化语法(8.5)或强制转换(5.2.9,5.4)的情况下才这样做。
这个问题可以通过以下更简短的例子来说明
struct A {
A() {}
explicit A(const A&) {}
};
int main() {
A a;
A b = a; // ERROR: copy-initialization
A c(a); // OK: direct-initialization
}
这就是阻止所有转换工作的原因,因为它们都依赖于复制初始化,而复制初始化又依赖于隐式复制。你禁用了隐式复制。
此外,请参阅涵盖此特定问题的Defect Report #152。虽然我不确定“提议的解决方案”应该是什么后果......
答案 1 :(得分:0)
正如您所提到的,gcc不会编译以下代码。
struct A {
A( int ) {}
explicit A( A const& ) {}
};
int main() {
A a = 2;
}
根据现行标准8.5 p15,我认为这不符合标准 但是,至于你的第一个案例,
struct A {
A( int ) {}
explicit A( A const& ) {}
};
struct B {
operator A() { return 2; }
};
int main() {
B b;
A a = b;
}
我不相信允许这一点符合要求
如您所知,return
将在概念上调用两次复制
return 2;
隐式地从A
2构造int
,并且它已经习惯了
直接初始化时间返回值(R)
但是,应将直接初始化应用于第二次复制
从R到a
?
我找不到当前标准中明确指出的措辞
直接初始化应该应用两次
因为可以肯定这个序列破坏了explicit
规范
感觉,即使编译器开发人员认为允许这一点,我也不会感到惊讶
是一个缺陷。
让我做一个不必要的补充
编译器的不符合行为并不意味着编译器具有
直接缺陷。
如缺陷报告所示,该标准已存在缺陷
例如,C标准不允许从指针转换为
一个T类型的数组,指向一个const
T的数组的指针
这在C ++中是允许的,我认为它应该在C语义上允许
与此类似。
Gcc发出有关此转换的警告。
Comeau发出错误,但不编译。