代码之家  ›  专栏  ›  技术社区  ›  Phidelux

基于整型的两类隐式转换

  •  2
  • Phidelux  · 技术社区  · 7 年前

    我有上课的机会 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
    }
    
    3 回复  |  直到 7 年前
        1
  •  2
  •   Yakk - Adam Nevraumont    7 年前

    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)) )
    {}
    

    或者你可以把你的 B UIT88T 在您要转换为 A .

    你也可以在 在那里你创造了一个魔法 template<class T, /*SFINAE magic*/> operator T 转换为任何可以由 UIT88T .

    这个模糊的代码:

      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 子句声明我们只需要可以转换为 UIT88T . 第二种状态是我们不希望将此构造函数用于 即使它通过了第一个。

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

    所使用的技术称为sfinae,或者替换失败不是错误。当一种 IntType 当这些测试失败时,在这些条款中有替换失败。通常,这会导致错误,但在评估模板重载时,这不是一个错误,因为sfinae;相反,它只是阻止在重载解决方案中考虑此模板。

        2
  •  2
  •   NathanOliver    7 年前

    隐式创建对象时,只允许一个用户定义的转换。自从 func 需要一个 A 您将有一个用户定义的转换来转换 B 变成一个 std::uint8_t 然后另一个用户定义的转换 性病:UIT88T 变成一个 . 你需要的是 operator A 在里面 或一个构造函数 那需要一个 如果你希望它隐式发生。否则,您只能显式强制转换,因此您只需要一个隐式强制转换,如

    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
    
        3
  •  2
  •   eerorika    7 年前

    这种行为是有意的吗?

    是的,这是故意的。

    隐式转换序列最多可以有一个用户定义的转换(构造函数或转换函数)。

    标准说(强调我的):

    [超过。ICS。用户]

    用户定义的转换序列由初始标准转换序列和 用户- 定义的转换(15.3),然后是第二个标准转换序列。…


    为了让用户定义的类型(类)隐式转换为另一个类型,必须有一个直接转换为该类型的构造函数或转换运算符。不能通过中间类型进行隐式转换(从用户定义的类型到另一个类型)。

    您可以改用显式转换。