【问题标题】:Copy initialization of class type by user defined conversion通过用户定义的转换复制类类型的初始化
【发布时间】:2013-09-04 23:03:09
【问题描述】:

我有以下代码sn-p:

struct T {
    T(const T&) = default;
    T(const S &);
};

struct S {
    operator T();
};

int main() {
    S s;
    T t = s; // copy-initialization of class type
    return 0;
}

我的问题是为什么编译器更喜欢 S::operator T() 来初始化 t 而不是报告初始化不明确的错误。在我看来,会发生以下情况(如果我错了,请纠正我):

  • t 使用 S 类型的左值进行复制初始化
  • S 不是 T,S 也不是 T 的子类,所以 S 和 T 不相关
  • 由于变量 t 是复制初始化的,并且类型 S 和 T 不相关,编译器会尝试找到用户定义的转换序列来进行初始化。
  • 重载解析负责选择最佳的用户定义转换,可以是T的转换构造函数,也可以是S的转换函数
  • 参数 s 的构造函数 T::T(const S&) 的隐式转换序列是恒等转换,因为左值 s 可以直接绑定到此左值引用
  • 参数 s 的转换函数 S::operator T() 的隐式转换序列也是恒等转换,因为隐式对象参数是 S&

构造函数和转换函数都返回一个 T 类型的纯右值,可用于直接初始化变量 t。这意味着两个自定义转换序列的第二个标准转换序列是恒等转换。

这意味着两个用户定义的转换序列同样好。还是有什么特殊的规则偏爱转换函数?

我正在阅读 c++11 标准中的以下规则:

表单中发生的初始化 Tx = 一个; 以及在参数传递、函数返回、抛出异常 (15.1)、处理异常 (15.3) 和聚合成员初始化 (8.5.1) 中称为复制初始化。

初始化器的语义如下...如果目标类型是(可能是 cv-qualified)类类型:如果初始化是直接初始化,或者如果是复制初始化,其中 cv-unqualified 源类型的版本与目标类是同一类或派生类, 构造函数被考虑...... 否则(即,对于剩余的复制初始化情况),可以从源类型转换到目标类型或(当使用转换函数时)到其派生类的用户定义转换序列被枚举,如 13.3 中所述。 1.4,通过重载决议选择最好的(13.3)

如果用户定义的转换序列U1包含相同的用户定义的转换函数或构造函数,并且U1的第二个标准转换序列优于第二个标准转换序列,则用户定义的转换序列U1是比另一个用户定义的转换序列U2更好的转换序列U2

也许我在做出错误的假设。我希望你能帮助我!

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    使用转换运算符从S 转换比使用S const 作为参数转换为T 更好。如果您将s 设为S const,则首选构造函数:您的身份操作 在一种情况下确实是身份操作,而在另一种情况下则不是。如果将S 的转换运算符设为const 成员,则会产生歧义。下面是一个演示所有情况的测试程序:

    struct S;
    struct T {
        T(const T&) = default;
        T(const S &);
    };
    
    struct S {
        S(); // needed to allow creation of a const object
    #ifdef AMBIGUOUS
        operator T() const;
    #else
        operator T();
    #endif
    };
    
    int main() {
    #ifdef CONST
        S const s;
    #else
        S s;
    #endif
        T t = s; // copy-initialization of class type
        return 0;
    }
    

    【讨论】:

    • 感谢您的示例。我想我找到了澄清这一点的规则。
    【解决方案2】:

    我想我找到了澄清这一点的规则:

    13.3.3.2: ... S1 和 S2 是引用绑定 (8.5.3),并且引用所引用的类型是相同的类型,除了顶级 cv 限定符和引用的类型由 S2 初始化的引用比由 S1 初始化的引用所引用的类型更具有 cv 限定。

    在成员函数 S::operator T() 中,隐式对象参数的类型为 S&,它直接绑定到类型 S 的左值 s。在构造函数 T::T(const S&) 中,参数直接绑定到S 类型的左值,但此引用绑定比运算符函数更符合 cv 要求,因此重载决议首选运算符函数。

    你同意吗?

    【讨论】:

    • 你需要做更多的工作来证明隐式对象参数是一个引用绑定。 operator T() 之后没有引用限定符。但我想你会在 Overloading 子句中对隐式对象参数的讨论中找到联系。
    猜你喜欢
    • 1970-01-01
    • 2011-09-25
    • 2015-07-03
    • 1970-01-01
    • 2017-09-13
    • 2011-04-02
    • 2018-10-23
    • 1970-01-01
    • 2016-01-15
    相关资源
    最近更新 更多