为什么(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("%cn",(false?A():B()).test());
return 0;
}
不能编译,但如果B
是A
的子类:
#include <stdio.h>
class A{
public:
char test(){
return 'A';
}
};
class B : public A{
public:
char test(){
return 'B';
}
};
int main(){
printf("%cn",(false?A():B()).test());
return 0;
}
可以编译,为什么?
因为(test?a:b)
是一个表达式,必须有一个类型。该类型是a和b的共同类型,而不相关的类型没有共同类型。基类和派生类的共同类型是基类。
请注意,问题包含一个假设,即编译的only情况是存在公共基类型的地方。事实上,如果存在从一种类型到另一种类型的明确转换,它也会编译。
条件运算符(?:
)操作数必须具有共同的类型。即给定E1 ? E2 : E3
,则E2
和E3
必须是明确可转换的。该类型然后作为返回类型用于整个表达式。
从cppreference中,他们列出了规则和需求,但这里重要的一行是;
条件操作符的返回类型也可以作为二进制类型trait
访问。std::common_type
这基本上是说必须有一个共同的类型,std::common_type
可以用来计算该类型。
基于你的代码片段(true ? a.test() : b.test())
工作,因为a.test()
和b.test()
返回char
。但是a
和b
是不相关的,因此不能单独使用。
c++ (WD n4527)标准中的相关资料见§5.16 ([expr.cond])。这里应用了几个规则和转换,其要点是如果没有转换,或者转换是不明确的,则程序是病态的。
如果条件运算符的第二个和第三个操作数不具有相同的"类型",则尝试将其中一个操作数转换为另一个操作数。如果不能进行这种转换或有歧义,则程序是病态的。
这会产生一些意想不到的结果,其中一个有趣的例子是在转换为函数指针后具有兼容函数签名的无捕获lambda,例如:
#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报告中看到,这也适用于一般情况,特别是对于这个问题,适用于没有公共基的类。下面的例子摘自bug报告:
struct A{ typedef void (*F)(); operator F(); };
struct B{ typedef void (*F)(); operator F(); };
void f() {
false ? A() : B();
}
是有效的,因为A
和B
都可以转换为函数指针,就像无捕获的lambda情况一样。
参考5.16
[expr]章节中的c++标准草案。气孔导度]说:
否则,如果第二个和第三个操作数具有不同的类型,并且其中一个具有(可能是cv限定的)类类型,或者如果两者都是相同值类别和相同类型的值(cv-qualification除外),则使用将尝试将其中每个操作数转换为另一个操作数的类型。
然后涵盖规则,然后说:
使用此过程,确定第二个操作数是否可以转换为与第三个操作数匹配操作数,以及是否可以将第三个操作数转换为匹配第二个操作数。如果两者都可以转换,或者可以转换,但转换是不明确的,程序是病态的。如果既不可以转换,操作数保持不变,并按如下所述执行进一步的检查。如果只能进行一次转换,则将该转换应用于所选操作数和转换后的对象
部分的剩余部分使用操作数代替原始操作数。
添加到语言中:
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{}
);
,只有当A
和B
都有一个.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++中没有办法用干净的语法创建惰性操作符,你只能使用内置的。
无论如何,你的代码不能工作,因为?:
是一个返回类型的表达式。没有可以同时表示A
和B
的类型,所以它不能工作。如果一个是另一个的基底,或者有转换等,那么它就可以工作。
- 继承期间显示未知行为的子类
- 通过指向指针数组的指针访问子类的属性
- 从父类方法返回子类对象
- c++, 在子类中,如何在没有对象的情况下访问父类的方法?
- 将父类对象强制转换为子类的问题
- 避免在C++中重复子类定义
- 将QOpenGLWidget子类转换为使用Metal而不是OpenGL的子类是否可行?
- 如何初始化矢量的模板化子类
- C++ 继承:将子类传递给需要基类的函数并获取子类行为
- 有没有办法按值将纯抽象类的所有子类传递给 C++ 中的函数?
- 使用子类覆盖基类中定义的函数
- 子类地址等于虚拟基类地址?
- 将子类方法声明为基类的友元
- C++子类共享变量?
- 如何检查模板专用化是否是基本模板的子类?
- 仅让特定类'Fabric'构造类'Foo'及其所有子类的实例
- C++ 访客模式与子类节点上的访客失去"is a"关系
- 如何将两个类之间的严重依赖关系表示为子类化的契约
- 为什么(false?A():B()).test()只有在A和B有子类关系时才能编译
- 堆栈和子类之间的正确UML关系是什么