C++ 基于积分类型的两类间隐式转换
在这种情况下,我有一个类C++ 基于积分类型的两类间隐式转换,c++,operator-overloading,implicit-conversion,C++,Operator Overloading,Implicit Conversion,在这种情况下,我有一个类a,它为整数类型提供构造函数,还有一个类B,它为相同的整数类型提供隐式转换运算符。但是,如果我调用一个函数,该函数接受对类a的引用,并带有类B的实例,则编译失败。我希望类B隐式转换为类A的构造函数所接受的类型。当然,如果我在a接受类B中添加一个构造函数,一切都很好。这种行为是故意的吗?请检查下面的示例 #include <iostream> class B { public: B() = default; B(const st
a
,它为整数类型提供构造函数,还有一个类B
,它为相同的整数类型提供隐式转换运算符。但是,如果我调用一个函数,该函数接受对类a
的引用,并带有类B
的实例,则编译失败。我希望类B
隐式转换为类A
的构造函数所接受的类型。当然,如果我在a
接受类B
中添加一个构造函数,一切都很好。这种行为是故意的吗?请检查下面的示例
#include <iostream>
class B
{
public:
B() = default;
B(const std::uint8_t &val) : mVal(val) {}
std::uint8_t get() const { return mVal; }
operator std::uint8_t() const { return mVal; }
private:
std::uint8_t mVal;
};
class A
{
public:
A() = default;
A(const std::uint8_t &val) : mVal(val) {}
// No problem if this exists
// A(const B &b) : mVal(b.get()) {}
std::uint8_t get() const { return mVal; }
private:
std::uint8_t mVal;
};
void func(const A &a)
{
std::cout << static_cast<int>(a.get()) << std::endl;
}
int main(int, char*[])
{
std::uint8_t val = 0xCE;
A a(val);
B b(val);
func(val); // fine
func(a); // fine
func(b); // error
}
#包括
B类
{
公众:
B()=默认值;
B(const std::uint8_t&val):mVal(val){}
std::uint8_t get()常量{return mVal;}
运算符std::uint8_t()常量{return mVal;}
私人:
标准:uint8_t mVal;
};
甲级
{
公众:
A()=默认值;
A(const std::uint8_t&val):mVal(val){}
//如果存在,没有问题
//A(constb&B):mVal(B.get()){}
std::uint8_t get()常量{return mVal;}
私人:
标准:uint8_t mVal;
};
无效函数(常数A&A)
{
标准::cout
这种行为是故意的吗
是的,这是有意的
隐式转换序列最多可以有一个用户定义的转换(构造函数或转换函数)
标准说(强调我的):
[over.ics.user]
用户定义的转换序列由初始标准转换序列和A用户组成-
定义的转换(15.3),然后是第二个标准转换序列
为了使用户定义的类型(类)可以隐式转换为另一个类型,必须有直接转换为该类型的构造函数或转换运算符。不能通过中间类型进行隐式转换(从用户定义的类型转换为另一个类型)
您可以使用显式转换。在隐式创建对象时,您只允许进行一次用户定义的转换。由于func
需要a
,您可以进行一次用户定义的转换,将B
转换为std::uint8\t
,然后进行另一次用户定义的转换,将std::ui>转换为nt8\u t
转换为A
。您需要的是B
中的操作符A
,或者A
中的构造函数,如果您希望隐式发生,它将接受B
。否则,您可以显式强制转换,这样您只需要一个隐式构造函数,如
func(static_cast<std::uint8_t>(b)); // force it to a uint8_t
// or
func({b}); // make b the direct initializer for an A which will implicitly cast
// or
func(A{b}); same as #2 above but explicitly sating it
func(static_cast(b));//强制将其转换为uint8\t
//或
func({b});//使b成为将隐式强制转换的A的直接初始值设定项
//或
func(A{b});与上面的#2相同,但显式满足它
在C++中有一个规则,即没有隐式转换将使用两个用户定义的转换。
这是因为这种“远距离”转换可能会产生极其令人惊讶的结果
如果您希望能够从任何可以转换为uint8\t
的内容转换,您可以执行以下操作:
template<class IntLike,
std::enable_if_t<std::is_convertible_v<IntLike, std::uint8_t>, bool> =true,
std::enable_if_t<!std::is_same_v<A, std::decay_t<IntLike>>, bool> =true
>
A( IntLike&& intlike ):A( static_cast<std::uint8_t>(std::forward<IntLike>(intlike)) )
{}
存在以确保仅当转换的类型具有所需的属性时才使用重载
第一个enable\u if
子句声明我们只需要可以转换为uint8\t
的东西。第二个声明我们不希望此构造函数用于类型A
本身,即使它通过了第一个
无论何时为类型创建转发引用隐式构造函数,都非常需要第二个子句,否则会遇到一些其他令人惊讶的问题
所使用的技术称为SFINAE,或者替换失败不是错误。当推导出类型IntType
,并且这些测试失败时,这些子句中存在替换失败。通常这会导致错误,但当评估模板重载时,这不是错误,因为SFINAE;相反,它只是阻止该模板从b在重载解析中考虑。只允许一个用户转换,这里需要2个。B
->std::uint8\t
->A
@Yksisarvinen严格来说,您的注释是错误的。您可以使用任何可以转换为AB
的内容。如果编译器需要AB
,它并不总是有to be aB
func({B})
甚至更短,但对于“另一方”(func(a{B})
)。@Jarod42也包括这些
std::enable_if_t<std::is_convertible_v<IntLike, std::uint8_t>, bool> =true,
std::enable_if_t<!std::is_same_v<A, std::decay_t<IntLike>>, bool> =true