提问者:小点点

构造函数和转换运算符之间的重载解析


我有几个与C++中的重载解析有关的问题。 请考虑以下示例:

extern "C" int printf (const char*, ...);                                       
                                                                                
struct X {};                                                                    
                                                                                
template <typename T>                                                           
struct A                                                                        
{                                                                               
    A() = default;                                                              
                                                                                
    template <typename U>                                                       
    A(A<U>&&)                                                                   
    {printf("%s \n", __PRETTY_FUNCTION__);}                                     
};                                                                              
                                                                                
template <typename T>                                                           
struct B : A<T>                                                                 
{                                                                               
    B() = default;                                                              
                                                                                
    template <typename U>                                                       
    operator A<U>()                                                             
    {printf("%s \n", __PRETTY_FUNCTION__); return {};}                          
};                                                                              
                                                                                
int main ()                                                                     
{                                                                               
    A<X> a1 (B<int>{});                                                         
} 

如果我用g++-std=C++11a.cpp编译它,将调用a的构造函数:

A<T>::A(A<U>&&) [with U = int; T = X] 

如果我用g++-std=C++17a.cpp编译程序,它将生成

B<T>::operator A<U>() [with U = X; T = int]

如果我将a(a&)注释出来,并再次用g++-std=C++11a.cpp编译它,则将调用转换运算符:

B<T>::operator A<U>() [with U = X; T = int]
  • 为什么在第三种情况下还要考虑转换运算符? 为什么程序不是格式不良的? [dcl.init]声明:

否则,如果初始化是直接初始化,或者是复制初始化,其中源类型的CV非限定版本与目标类型的类相同,或者是目标类型的派生类,则考虑构造函数。 枚举适用的构造函数(16.3.1.3),通过重载解析(16.3)选择最佳的构造函数。 调用这样选择的构造函数来初始化对象,使用初始值设定项表达式或表达式列表作为参数。 如果没有应用构造函数,或者重载解析不明确,则初始化格式不正确。

  • 为什么在第一种情况下a的构造函数是更好的选择?b的转换运算符似乎是更好的匹配,因为它不需要从ba的隐式转换。
  • 为什么第一种情况和第二种情况会产生不同的结果? C++17有什么变化?

附注。 有人知道我在哪里可以找到详细的指南,描述转换运算符如何参与重载解析,即当发生不同类型的初始化时,它们与构造函数交互的方式。 我知道标准提供了最准确的描述,但似乎我对标准措辞的解释与它的正确含义没有多少共同之处。 一些经验法则和其他示例可能会有帮助。


共1个答案

匿名用户

我相信这是公开的

  • CWG2327:使用转换功能复制省略以进行直接初始化

考虑这样一个例子:

struct Cat {};
struct Dog { operator Cat(); };

Dog d;
Cat c(d);

这是11.6[dcl.init]项目符号17.6.2:[...]

重载解析选择cat的移动构造函数。 初始化构造函数的cat&&参数会产生一个临时值,参见11.6.3[dcl.init.ref]项目符号5.2.1.2。 这就排除了这种情况下复制删除的可能性。

这似乎是保证复制删除的措辞变化中的一个疏忽。 在这种情况下,我们应该同时考虑构造函数和转换函数,就像在复制初始化时一样,但我们需要确保这不会引入任何新的问题或歧义。