这个代码不应该抛出一个模糊的转换错误吗? [英] Shouldn't this code throw an ambiguous conversion error?
问题描述
我有两个类, 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; //这个const不改变这个代码的输出
int x;
};
struct B {
B(const double& n):x(n){}
B(const A& a);
double x;
};
A :: operator B()const //此const不会更改此代码的输出
{
cout< 称为A的转换运算符< endl;
return B(double(x));
}
B :: B(const A& a)
{
cout< 称为B的转换构造函数< endl;
x =(double)a.x;
}
int main(){
A a(10);
static_cast< B>(a); //打印B的转换构造函数
}
对于用户定义的转换序列;在转换构造函数和转换运算符之间似乎没有优先级,它们都是候选者;
§13.3.3.1.2/ 1 定义的转换序列
用户定义的转换序列由一个初始标准转换序列和一个用户定义的转换12.3),随后是第二标准转化序列。如果用户定义的转换由构造函数(12.3.1)指定,则初始标准转换序列将源类型转换为构造函数的参数所需的类型。如果用户定义的转换由转换函数(12.3.2)指定,则初始标准转换序列将源类型转换为转换函数的隐式对象参数。
因此,如果转换已经;
B b2 = a; // ambiguous?
这可能不明确,编译失败。 Clang失败了编译,g ++接受代码并使用构造函数; 演示代码,VS也接受代码。 VS和g ++调用转换构造函数(根据OP代码)。
考虑到发布的代码,用户定义的转换序列(通过构造函数和转换运算符)
§5.2.9/ 4 静态强制转换
strong>
表达式e可以显式转换为
T
T t(e)的形式
对于一些发明的临时变量static_cast< T>(e)
的static_cast
);t
(8.5)是合理的。这种显式转换的效果与执行声明和初始化,然后使用临时变量作为转换结果相同。e
作为一个glvalue,当且仅当初始化使用它作为一个左值。
从上面的引用, static_cast
等价于 B temp(a);
$ b
§13.3.1.3/ 1 通过构造函数初始化 $ b
当类类型的对象被直接初始化(8.5),从相同或派生类类型(8.5)的表达式复制初始化,或者默认初始化(8.5) ,重载分辨率选择构造函数。对于直接初始化或默认初始化,候选函数是被初始化的对象的类的所有构造函数。对于复制初始化,候选函数是该类的所有转换构造函数(12.3.1)。参数列表是初始化器的表达式列表或赋值表达式。
一般来说(排除任何构造函数和标记为 explicit
和 const
关注),给定 B(const A& a);
构造函数和从 A
构造 B
,构造函数应该胜出,因为它提供了在考虑最佳可行功能时匹配;因为不需要进一步的隐式转换(§13.3;过载解决)。
如果构造函数 B(const A& a);
,因为用户定义的转换运算符是候选项并且其使用不含糊,所以转换(与 static_cast
§13.3.1.4/ 1 按用户定义的转换类复制初始化
在8.5中指定的条件下,作为类类型对象的复制初始化的一部分,可以调用用户定义的转换将初始化器表达式转换为正在初始化的对象的类型。
引用来自C ++标准的N4567草稿。
在一个对象的构造之外调用一个用户定义的转换序列,也就是调用一个方法也是有益的。
给定代码(和上述规则);
#include< iostream>
using namespace std;
struct A;
struct B {
B(){}
B(const A&){cout< 称为B的转换构造函数< endl; }
};
struct A {
A(){}
operator B()const {cout< 称为A的转换运算符< 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.用户定义的转换
}
Clang,g ++(演示)和VS提供不同的结果,从而可能有不同的合规性水平。
- clang失败2.和4。
- g ++接受1到4。
- VS失败4。
从上面的规则,1.到3.应该都成功,因为 code>转换构造函数是候选项,不需要进一步的用户转换;直接构造和复制初始化用于那些形式。从标准(上面摘录,特别是§13.3.3.1.2/ 1和§13.3.1.4/ 1,然后是§8.5/ 17.6.2),2.和4.可以/应该失败,并且模糊 - 因为转换构造函数和转换运算符被认为没有明确的顺序。
我相信这可能是一个意想不到的用例(类型能够彼此转换以这种方式;有一个论点,在那里将有一个转换序列将是一般的使用情况)。
I have two classes, A
and B
, each defining a conversion to B
. A
has a conversion operator to B
, B
has a constructor from A
. Shouldn't a call to static_cast<B>
be ambiguous? Using g++ this code compiles and chooses the conversion constructor.
#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
}
For user defined conversion sequences; there does not seem to a precedence given between the converting constructor and the conversion operator, they are both candidates;
§13.3.3.1.2/1 User-defined conversion sequences
A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user- defined conversion (12.3) followed by a second standard conversion sequence. If the user-defined conversion is specified by a constructor (12.3.1), the initial standard conversion sequence converts the source type to the type required by the argument of the constructor. If the user-defined conversion is specified by a conversion function (12.3.2), the initial standard conversion sequence converts the source type to the implicit object parameter of the conversion function.
Hence if the conversion had been;
B b2 = a; // ambiguous?
It could be ambiguous and the compilation fail. Clang fails the compilation, g++ accepts the code and uses the constructor; demo code, VS also accepts the code. VS and g++ call the converting constructor (as per the OP code).
In consideration of the posted code, the user defined conversion sequences (by constructor and converting operator) and the use of static_cast
need to be considered.
§5.2.9/4 Static cast
An expression e can be explicitly converted to a type
T
using astatic_cast
of the formstatic_cast<T>(e)
if the declarationT t(e);
is well-formed, for some invented temporary variablet
(8.5). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The expressione
is used as a glvalue if and only if the initialization uses it as a lvalue.
From the above quote, the static_cast
is equivalent to B temp(a);
and as such, the direct initialisation sequence is used.
§13.3.1.3/1 Initialization by constructor
When objects of class type are direct-initialized (8.5), copy-initialized from an expression of the same or a derived class type (8.5), or default-initialized (8.5), overload resolution selects the constructor. For direct- initialization or default-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization, the candidate functions are all the converting constructors (12.3.1) of that class. The argument list is the expression-list or assignment-expression of the initialiser.
In general (excluding any constructors and operators marked as explicit
and const
concerns), given the B(const A& a);
constructor and the construction of a B
from an A
, the constructor should win since it offers the exact match when considering the best viable function; since further implicit conversions are not needed (§13.3; Overload resolution).
If the constructor B(const A& a);
was removed, the conversion (with the static_cast<>
would still succeed since the user defined conversion operator is a candidate and its use is not ambiguous.
§13.3.1.4/1 Copy-initialization of class by user-defined conversion
Under the conditions specified in 8.5, as part of a copy-initialization of an object of class type, a user-defined conversion can be invoked to convert an initializer expression to the type of the object being initialized.
Quotes are taken from the N4567 draft of the C++ standard.
It would also be instructive to invoke a user-defined conversion sequence outside just the construction of an object, i.e. calling a method.
Given the code listing (and the rules above);
#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
}
Clang, g++ (demo) and VS offer different results and thus possibly different levels of compliance.
- clang fails 2. and 4.
- g++ accepts 1. through 4.
- VS fails 4.
From the rules above, 1. through 3. should all succeed since the B
converting constructor is a candidate and requires no further user conversions; direct construction and copy initialisation is used for those forms. Reading from the standard (the excerpts above, in particular §13.3.3.1.2/1 and §13.3.1.4/1, and then §8.5/17.6.2), 2. and 4. could/should fail and be ambiguous - since the conversion constructor and the conversion operator are being considered with no clear ordering.
I believe this may well be an unintended use case (types being able to convert to each other in this way; there is an argument for where there would be one conversion sequence would be the general use case).
这篇关于这个代码不应该抛出一个模糊的转换错误吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!