【问题标题】:Why can (false?A():B()).test() compile only when A and B have a subclass relationship?为什么 (false?A():B()).test() 只有 A 和 B 有子类关系时才能编译?
【发布时间】:2015-10-02 16:56:04
【问题描述】:

原来我喜欢用这样的东西:

(true?a:b).test()

而不是

(true?a.test():b.test())

如果函数名称相同,为了节省打字时间,一开始我以为应该是有效的,但我发现:

#include <stdio.h>
class A{
public:
    char test(){
        return 'A';
    }
};

class B{
public:
    char test(){
        return 'B';
    }
};

int main(){
    printf("%c\n",(false?A():B()).test());
    return 0;
}

无法编译,但如果BA 的子类:

#include <stdio.h>
class A{
public:
    char test(){
        return 'A';
    }
};

class B : public A{
public:
    char test(){
        return 'B';
    }
};

int main(){
    printf("%c\n",(false?A():B()).test());
    return 0;
}

它可以编译,为什么?

【问题讨论】:

  • 因为您尝试返回的对象必须符合相同的接口,在您的情况下它必须实现test 方法。编译器必须确保在任何情况下返回的对象都与接口一致。
  • 因为 C++ 并没有真正进行方法的动态调度(不像 python)。
  • 请注意,如果test 结果没有 common_type,true?a.test():b.test() 也会失败。
  • 条件运算符工作的另一个有趣案例是在转换为函数指针后使用具有兼容签名的无捕获 lambda,请参阅:MSVC error when using capture-less lambda expressions as second and third operand of conditional operator

标签: c++ inheritance syntax ternary-operator


【解决方案1】:

原因是(test?a:b)是一个表达式,必须有一个类型。该类型是a和b的共同类型,不相关的类型没有共同的类型。基类和派生类的共同类型是基类。

请注意,该问题包含一个假设,即编译的 only 案例是存在通用基本类型的地方。事实上,如果从一种类型到另一种类型的明确转换,它也会编译。

【讨论】:

  • 如果AB 都派生自C,那么test ? A() : B() 会起作用吗?
  • 根据这个答案,是的(假设函数test()C中定义并在AB中重载)。将(test?A():B()) 符号视为Type Iff(bool conditionExpression, Type trueResult, Type falseResult) { if (conditionExpression) return trueResult; else return falseResult; } 之类的函数的别名。然后调用Iff(test, a, b).test(); 更有意义。
  • @BlueRaja-DannyPflughoeft:听起来合乎逻辑,不。我没有 C++ 的 Design&Evolution 方便,但我认为这是因为 C++ 具有多重继承。对于单继承,找到继承链的 2 个根,然后从那里比较两个链的分叉点是一件简单的事情。使用 MI,您必须将每个根与每个根进行比较,并处理歧义。
【解决方案2】:

条件运算符 (?:) 操作数必须具有通用类型。 IE。给定E1 ? E2 : E3,那么E2E3 必须是明确可转换的。这个类型就是返回类型,然后作为一个整体用于表达式。

来自cppreference,他们列出了规则和要求,但与此相关的一条重点是:

条件运算符的返回类型也可以作为二元类型特征std::common_type进行访问

这基本上是说必须有一个共同的类型,而std::common_type可以用来计算那个类型。

根据您的代码 sn-p (true ? a.test() : b.test()) 有效,因为 a.test()b.test() 返回 char。但是ab 不相关,不能单独使用。


在C++(WD n4527)标准中找到相关资料是§5.16 ([expr.cond])。应用了多种规则和转换,其要点是如果没有转换,或者如果转换不明确,则程序格式错误

【讨论】:

    【解决方案3】:

    如果条件运算符的第二个和第三个操作数不具有相同的 "type",则尝试将其中一个操作数转换为另一个操作数。如果这种转换无法进行或不明确,则程序格式错误。

    这有一些可能看起来意想不到的结果,其中一个有趣的案例是capturless lambda with compatible function signature after conversion to a function pointer,例如:

    #include <iostream>
    #include <algorithm>
    #include <vector>
    
    int main() {
        std::vector<int> v(0, 10);
        bool increase = true;
        std::sort(v.begin(), v.end(), increase ? 
              [](int lhs, int rhs){return lhs < rhs;} : 
              [](int lhs, int rhs){return lhs > rhs;} );
        return 0;
    } 
    

    有效。我们可以从下面的bug report 中看到,这也适用于一般情况,特别是对于这个问题适用于没有共同基础的类。以下示例取自错误报告:

    struct A{ typedef void (*F)(); operator F(); };
    struct B{ typedef void (*F)(); operator F(); };
    
    void f() {
      false ? A() : B();
    }
    

    是有效的,因为就像无捕获的 lambda 情况一样,AB 都可以转换为函数指针。

    供参考5.16 部分中的 C++ 标准草案 [expr.cond] 说:

    否则,如果第二个和第三个操作数有不同的类型并且有(可能是 cv 限定的)类 类型,或者如果两者都是相同值类别和相同类型(除了 cv 限定)的泛左值,则 尝试将这些操作数中的每一个转换为另一个的类型。

    然后涵盖了规则然后说:

    使用这个过程,判断第二个操作数是否可以转换为匹配第三个 操作数,以及第三个操作数是否可以转换为匹配第二个操作数。如果两者都可以 转换,或者可以转换但转换不明确,程序格式错误。如果两者都没有 可以转换,操作数保持不变,进一步检查如下所述。 如果恰好可以进行一次转换,则该转换将应用于所选操作数和转换后的 本节其余部分使用操作数代替原始操作数

    【讨论】:

      【解决方案4】:

      将其添加到语言中:

      template<class F>
      struct if_t {
        bool b;
        F f;
        template<class Lhs, class Rhs>
        auto operator()(Lhs&&lhs, Rhs&&rhs)&&
        ->std::result_of_t<F(Lhs)>
        {
          if (b) return std::forward<F>(f)(std::forward<Lhs>(lhs));
          else return std::forward<F>(f)(std::forward<Rhs>(rhs));
        }
        template<class Lhs>
        void operator()(Lhs&&lhs)&&
        {
          if (b)
            std::forward<F>(f)(std::forward<Lhs>(lhs));
        }
      };
      
      template<class F>
      if_t<std::decay_t<F>> branch(bool b, F&& f){
        return {b,std::forward<F>(f)};
      }
      

      然后我们得到:

      branch(false, [&](auto&&arg){return arg.test();})
      (
        A{}, B{}
      );
      

      只有在AB 都有.test() 并且B::test() 的返回值可以转换为A::test() 的返回值时才有效。

      遗憾的是,A{}B{} 都被构造了。

      template<class T>
      auto make(){return [](auto&&...args){return {decltype(args)(args)...};}}
      
      branch(false, [&](auto&&arg){return arg().test();})
      (
        make<A>(), make<B>()
      );
      

      结构不同。

      不是最优雅的语法。它可以清理一些,但还不够(在我看来)。没有办法在 C++ 中用干净的语法创建惰性运算符,你只能使用内置的。

      无论如何,您的代码无法工作,因为?: 是一个返回类型的表达式。没有一种类型可以同时表示AB,因此它无法工作。如果一个是另一个的基础,或者有转换等,那么它会起作用。

      【讨论】:

        猜你喜欢
        • 2011-08-10
        • 1970-01-01
        • 2018-06-27
        • 1970-01-01
        • 2012-07-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多