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

理解C++转换算子的选择

  •  3
  • gct  · 技术社区  · 6 年前

    我有一个可选的类(我不能使用可选的,因为它是在C++ 17中)。它包含一个(可能的)值以及一个标志,指示它是否有效。我有一个显式的bool运算符和一个转换运算符来获取值。问题是,有时C++将在显式布尔上下文(ANF语句)中选择布尔运算符,而其他时候它不会。任何人能帮助我理解这种行为:

    #include <algorithm>
    #include <stdexcept>
    
    template <typename T>
    struct maybe {
        maybe()        : valid_(false) {}
        maybe(T value) : valid_(true)  { new (&value_) T(value); }
    
        operator T&&() {
            return std::move(value());
        }
    
        explicit operator bool() const {
            return valid_;
        }
    
        T& value() {
            if (!valid_) {
                throw std::runtime_error("boom");
            }
            return value_;
        }
    
    private:
        union {
            T value_;
        };
        bool valid_;
    };
    
    
    int main() {
        // fine, uses operator bool()
        maybe<std::pair<int,int>> m0;
        if (m0) {
            std::pair<int,int> p = m0;
            (void)p;
        }
    
        // throws error, uses operator T&&()
        maybe<double> m1;
        if (m1) {
            double p = m1;
            (void)p;
        }
    }   
    
    3 回复  |  直到 6 年前
        1
  •  4
  •   Barry    6 年前

    无论何时写:

    if (x)
    

    相当于写了:

    bool __flag(x);
    if (__flag)
    

    这被称为上下文转换 bool (请注意,它是直接初始化的,因此 explicit 转换函数是一个候选函数)。

    当我们对那个结构进行过载解决时, m0 ,只有一个有效的候选人: explicit operator bool() const .

    但当我们对那个建筑做超载决议 m1 ,有两个: 显式运算符bool()const operator double&&() ,因为 double 可转换为 布尔 . 后者是一个更好的匹配,因为额外的 const 资格 布尔 转换函数,即使我们必须做一个额外的 双重的 转换。因此,它赢了。

    只需移除 operator T&&() 从您的界面,因为它对这种类型没有多大意义。

        2
  •  1
  •   YSC    6 年前

    一旦 T 可转换为 bool ( double is , std::pair 不是)您的两个运算符将匹配,您将得到一个不明确的调用,或者出于某种原因,其中一个可能是更好的匹配。

    您应该只提供两个运算符中的一个,而不是同时提供两个。

        3
  •  1
  •   Taylor Nichols    6 年前

    你的 operator bool 在这种设计中,转换运算符是不明确的。

    • 在第一个上下文中, std::pair<int,int> 铸造成 bool 因此使用显式bool转换运算符。

    • 在第二种情况下, double 铸造成 布尔 所以 T 使用转换运算符,它返回 双重的 ,然后 铸成 布尔 隐含地。

    注意,在第二个上下文中,您正在调用 std::move 这放 value 处于有效但未定义的状态,这将导致在强制转换时出现未定义的行为 价值 双重的 第二次在 if 块。

    我将使用命名成员函数来指示它是否有效,并修改转换运算符:

    #include <algorithm>
    #include <stdexcept>
    
    template <typename T>
    struct maybe {
        maybe()        : valid_(false) {}
        maybe(T value) : valid_(true)  { new (&value_) T(value); }
    
        operator T&&() && { return std::move(value_); } // if rvalue
        operator T&() & { return value_; } // if lvalue
        operator const T&() const & { return value_; } // if const lvalue
    
        bool valid() const { return valid_; }
    
        T& value() {
            if (!valid_) {
                throw std::runtime_error("boom");
            }
            return value_;
        }
    
    private:
        union {
            T value_;
        };
        bool valid_;
    };
    
    int main() {
        // fine, uses operator bool()
        maybe<std::pair<int,int>> m0;
        if (m0.valid()) {
            std::pair<int,int> p = m0;
            (void)p;
        }
    
        // throws error, uses operator T&&()
        maybe<double> m1;
        if (m1.valid()) {
            double p = m1;
            (void)p;
        }
    }   
    

    编辑:转换运算符只能从成员移动 value_ 如果 maybe 对象是右值引用。使用 && 在函数签名专门用于这个案例之后——请参见 Kerrek SB's answer 更多信息。