首页 > 解决方案 > 基于整数类型的两个类之间的隐式转换

问题描述

我有一个类A,它为整数类型提供构造函数,以及B为相同整数类型提供隐式转换运算符的类。但是,如果我调用一个函数来接受对具有类A实例的类的引用B,则编译将失败。我本来期望将 class 隐式转换为 classB的构造函数接受的类型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
}

标签: c++operator-overloadingimplicit-conversion

解决方案


C++ 中有一条规则,即没有隐式转换将使用两个用户定义的转换。

这是因为这种“长距离”转换可能会导致极其令人惊讶的结果。

如果您希望能够从任何可以转换为 a 的东西转换,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)) )
{}

或者您可以Buint8_t要转换为A.

您可以做类似的事情,在B其中创建一个template<class T, /*SFINAE magic*/> operator T可以转换为任何可以由uint8_t.

这个晦涩的代码:

  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

存在以确保仅当我们要转换的类型具有我们想要的属性时才使用重载。

第一个enable_if子句声明我们只想要可以转换为uint8_t. 第二个状态我们不希望这个构造函数用于类型A本身,即使它通过了第一个。

每当您为类型创建转发引用隐式构造函数时,第二个子句非常需要,否则您会遇到其他一些令人惊讶的问题。

使用的技术称为 SFINAE 或替换失败不是错误。当IntType推导出一个类型并且这些测试失败时,这些子句中存在替换失败。通常这会导致错误,但在评估模板重载时它不是错误,因为 SFINAE;相反,它只是阻止此模板在重载决议中被考虑。


推荐阅读