为什么(false?A():B()).test()只有A和B有子类关系才能编译?

Why can (false?A():B()).test() compile only when A and B have a subclass relationship?

本来我喜欢用这样的:

(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?a:b)是一个表达式,必须有一个类型。该类型是 a 和 b 的公共类型,不相关的类型没有公共类型。 base 和 derived class 的共同类型是 base class.

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

条件运算符 (?:) 操作数必须具有共同类型。 IE。给定 E1 ? E2 : E3E2E3 必须明确可转换。这种类型就是 return 类型,然后作为一个整体用于表达式。

cppreference 开始,他们列出了规则和要求,但与此处相关的一条显着线是;

The return type of a conditional operator is also accessible as the binary type trait std::common_type

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

根据您的代码片段,(true ? a.test() : b.test())a.test()b.test() returned char 以来一直有效。但是 ab 是无关的,因此不能单独使用。


C++中的相关material(WD n4527) standard is found is §5.16 ([expr.cond])。有几个规则和转换被应用,其要点是如果没有转换,或者如果转换不明确,则程序格式错误.

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

这对某些人来说似乎是意想不到的结果,其中一个有趣的案例是 ,例如:

#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 [expr.cond] 部分的 C++ 标准草案供参考:

Otherwise, if the second and third operand have different types and either has (possibly cv-qualified) class type, or if both are glvalues of the same value category and the same type except for cv-qualification, an attempt is made to convert each of those operands to the type of the other.

然后覆盖规则然后说:

Using this process, it is determined whether the second operand can be converted to match the third operand, and whether the third operand can be converted to match the second operand. If both can be converted, or one can be converted but the conversion is ambiguous, the program is ill-formed. If neither can be converted, the operands are left unchanged and further checking is performed as described below. If exactly one conversion is possible, that conversion is applied to the chosen operand and the converted operand is used in place of the original operand for the remainder of this section

将其添加到语言中:

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() 的 return 值可以转换为 return 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++ 中用干净的语法创建惰性运算符,你只能使用内置的。

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