考虑以下玩具代码:
class Y
{
public:
Y(int, int) { cout << "Y ctor\n"; }
};
class X
{
public:
//X(initializer_list<int>) { cout << "init\n"; } // 1
X(Y&&) { cout << "Y&&\n"; } // 2
X(int, int) { cout << "param\n"; } // 3
X(const X&) { cout << "X&\n"; } // 4
X(const Y&) { cout << "Y&\n"; } // 5
X(Y) { cout << "Y\n"; } // 6
X(X&&) { cout << "X&&\n"; } // 7
};
void f(const X&) { }
void f(const Y&) { }
void f(Y) { }
int main()
{
X x({1,2});
//f({ 1,2 }); // 8
}
我试图了解编译器如何使用{1,2}
in X x({1,2})
。以下是我的理解:
-
取消注释行1
在这种情况下,我认为X x({1,2})
是一个明确的呼吁X(initializer_list<int>)
。也就是说,当编译器看到{}
-list,我想它首先会看到它std::initializer_list
和一个带有参数的构造函数initializer_list<T>
将是比所有其他人更好的匹配。
-
注释掉行6
, 7
在这种情况下,我认为{1,2}
用于构造一个对象Y
它与右值引用绑定(即编译器选择调用X(Y&&)
)。我的猜测是编译器会处理{1,2}
当没有构造函数接受参数时,作为某种右值initializer_list<T>
,因此首选采用右值引用的构造函数(在本例中,X(Y&&)
是此类的唯一构造函数)。
Update:我发现我在这个要点中描述的行为实际上是bug https://godbolt.org/z/ozad3aTxxMSVC 的。 gcc 和 clang 会报告歧义,不调用X(Y&&)
-
注释掉行7
出现歧义。编译器报告两者X(Y&&)
and X(Y)
匹配。我认为这是因为虽然X(Y&&)
是一个比更好的匹配X(const Y&)
and X(const X&)
, 无法区分X(Y)
。 (我觉得这里的逻辑很扭曲)
可能的参考 https://stackoverflow.com/a/17961890/16500459.
-
注释掉行2
,7
代码编译并打印param
。我想这一次X(const X&)
被选择但我不知道为什么。我对此的猜测是,当所有可行的匹配时(X(const X&)
, X(const Y&)
and X(Y)
)是无法区分的,编译器选择X(const X&)
因为X x({1,2});
正在建造一个X
.
Update:这也可以是 MSVCbug https://godbolt.org/z/TPbx7r7aE。 gcc 和 clang 会报告歧义,不调用X(const X&)
-
注释掉行2
,7
并取消注释行8
我认为在这种情况下,所有可行的匹配都是无法区分的,并且f
不是一个构造函数X
, hence f(const X&)
没有得到特殊对待,就会产生歧义。
请问我的理解是否正确? (我非常怀疑它们是否准确)
我发现读取构造函数被诸如此类的东西调用是相当乏味的X x({1,2});
,所以我想在实际代码中我们应该尽量避免这种速记并以更明确的方式编写代码......
另一方面,如果我们对类使用相同的设置X
and Y
如上,定义并调用一个函数g
如下:
X g()
{
return { 1,2 };
}
int main()
{
g();
}
结果似乎总是等同于通过以下方式初始化返回的临时值X{1,2}
(即致电X(initializer_list<int>)
当它存在时,并且在所有其他情况下调用X(int, int)
)。请问这是真的吗?