这段代码不应该抛出一个模糊的转换错误吗?

ror*_*oro 14 c++ casting

我有两个班,A并且B,每个定义转换B.A有一个转换运算符B,B有一个构造函数A.不应该打电话static_cast<B>暧昧吗?使用g ++,这段代码编译并选择转换构造函数.

#include<iostream>

using namespace std;

struct B;
struct A {
    A(const int& n) : x(n) {}
    operator B() const;         //this const doesn't change the output of this code
    int x;
};

struct B{
    B(const double& n) : x(n) {}
    B(const A& a);
    double x;
};

A::operator B() const           //this const doesn't change the output of this code
{
    cout << "called A's conversion operator" << endl;
    return B(double(x));
}

B::B(const A& a)
{
    cout << "called B's conversion constructor" << endl;
    x = (double) a.x;
}

int main() {
    A a(10);
    static_cast<B>(a);            // prints B's conversion constructor
}
Run Code Online (Sandbox Code Playgroud)

Nia*_*all 13

对于用户定义的转换序列; 在转换构造函数和转换运算符之间似乎没有给出优先级,它们都是候选者;

§13.3.3.1.2/ 1 用户定义的转换序列

用户定义的转换序列包括初始标准转换序列,然后是用户定义的转换(12.3),后跟第二个标准转换序列.如果用户定义的转换由构造函数(12.3.1)指定,则初始标准转换序列将源类型转换为构造函数的参数所需的类型.如果转换函数(12.3.2)指定了用户定义的转换,则初始标准转换序列会将源类型转换为转换函数的隐式对象参数.

因此,如果转换是;

B b2 = a; // ambiguous?
Run Code Online (Sandbox Code Playgroud)

它可能含糊不清,编译失败.Clang无法编译,g ++接受代码并使用构造函数; 演示代码,VS也接受代码.VS和g ++调用转换构造函数(根据OP代码).

考虑到发布的代码,用户定义的转换序列(通过构造函数和转换运算符)和static_cast需要考虑使用.

§5.2.9/ 4 静态演员

对于某些发明的临时变量(8.5),如果声明格式正确,则表达式e可以T使用static_cast表单的类型显式转换为类型.这种显式转换的效果与执行声明和初始化,然后使用临时变量作为转换结果相同.当且仅当初始化将其用作左值时,该表达式才用作glvalue.static_cast<T>(e)T t(e);te

从上面的引用中,static_cast等同于B temp(a);并且因此使用直接初始化序列.

§13.3.1.3/ 1 由构造函数初始化

当类类型的对象被直接初始化(8.5),从相同或派生类类型(8.5)或默认初始化(8.5)的表达式进行复制初始化时,重载决策选择构造函数.对于直接初始化或默认初始化,候选函数是正在初始化的对象的类的所有构造函数.对于复制初始化,候选函数是该类的所有转换构造函数(12.3.1).参数列表是初始化器的表达式列表或赋值表达式.

一般而言(不包括任何构造函数和标记为运营商explicitconst问题),给出的B(const A& a);构造和的建设B从一A,构造函数应该赢,因为它提供了考虑,当精确匹配最佳的可行函数 ; 因为不需要进一步的隐式转换(§13.3;重载分辨率).


如果B(const A& a);删除了构造函数,那么转换(static_cast<>由于用户定义的转换运算符是候选者并且其使用不明确,因此仍然会成功).

§13.3.1.4/ 1 通过用户定义的转换对类进行复制初始化

在8.5中指定的条件下,作为类类型对象的复制初始化的一部分,可以调用用户定义的转换以将初始化表达式转换为要初始化的对象的类型.

报价取自C45标准的N4567草案.


在对象构造之外调用用户定义的转换序列(即调用方法)也是有益的.

鉴于代码清单(以及上述规则);

#include <iostream>
using namespace std;
struct A;
struct B {
    B() {}
    B(const A&) { cout << "called B's conversion constructor" << endl; }
};
struct A {
    A() {}
    operator B() const { cout << "called A's conversion operator" << endl; return B(); }
};
void func(B) {}
int main() {
    A a;
    B b1 = static_cast<B>(a); // 1. cast
    B b2 = a; // 2. copy initialise
    B b3 ( a ); // 3. direct initialise
    func(a); // 4. user defined conversion
}
Run Code Online (Sandbox Code Playgroud)

Clang,g ++(演示)和VS提供不同的结果,因此可能有不同的合规级别.

  • clang失败2.和4.
  • g ++接受1.到4.
  • VS失败4.

从上面的规则来看,1到3都应该成功,因为B转换构造函数是候选者,不需要进一步的用户转换; 这些表格使用直接构造和复制初始化.从标准读取(摘录以上,特别§13.3.3.1.2/ 1和§13.3.1.4/ 1,然后第8.5节/ 17.6.2),2.和4可以/应该失败,是不明确的-因为正在考虑转换构造函数和转换运算符,没有明确的顺序.

我相信这可能是一个非预期的用例(类型能够以这种方式相互转换;有一个论点,即一个转换序列将是一般用例).