c++中的空传播操作符
Null propagation operator in C++
是否有人在c++中实现了null传播操作符,类似于在函数式语言中使用的操作符?我正在考虑一些聪明的模板解决方案,可能类似于操作符->的传播行为。
假设我们有一个图中的对象链,比如foo->bar->baz
(不好意思,是得墨忒耳定律)。假设其中任何一个都可能为空,并且应该在解引用之前进行测试。然后代码突然变得复杂多了:
if( !foo )
return nullptr;
if( !foo->bar )
return nullptr;
return foo->bar->baz;
我想用某种紧凑的语法来"分解"空检查,像这样:
foo?->bar?->baz // imaginary null-propagation syntax
现在,当然它不需要看起来像那样,只要尽可能紧凑就可以了。我想需要的是c++中的单子,启用null测试和"延续"。避免使用宏和lambda固然很好,但这可能是不可能的。我可以想象在每一步都重载操作符->(),如果this
为空则短路。
但是这太冒昧了。理想的解决方案是将每个对象包装在链中。
在设计的这个阶段,它可能是一个激进的背离,但也许可以考虑使用空对象模式。
;我非常怀疑c++能做到这一点。
不幸的是,我不相信这可以做到(没有预处理器)。我的代码总是看起来像
type function(type parent) {
if (parent==nullptr || parent->child==nullptr)
return null;
//do stuff with parent->child->grandchild
}
事实上,你提到"在每一步重载操作符->(),如果这是空的短路",但我不认为甚至可以完成(没有预处理器)。如果它是重载的,你可以让operator->()
返回null,但是如果this
是null
,那么调用operator->()
已经是未定义的行为。即使不是,它也可能已经返回了null
。你可以让它抛出一个异常,但我认为这个路径要比我上面提到的代码复杂得多。
使用预处理器,可能会有一些东西,但我的第一个想法是丑陋和危险的
#define MAYBENULL1(X, Y) (X?(X->Y,nullptr)
#define MAYBENULL2(X, Y, Z) (X&&X->Y?X->Y->Z,nullptr)
#define MAYBENULL3(X, Y, Z, W) (X&&X->Y&&X->Y->Z?X->Y->Z->W,nullptr)
type function(type parent) {return MAYBENULL2(parent, child, grandchild);}
但是你知道总会有人把事情搞砸的。
parent* get_next_parent();
type function() {return MAYBENULL1(get_next_parent(), child);}
在c#中有两种类型的null传播。第一种是如您所描述的,如果foo
为空,则返回空,但如果foo
不为空,则返回foo->bar
。
这在c++中已经不是那么复杂或难以阅读了。
return (foo) ? (foo->bar) ? foo->bar->baz : nullptr : nullptr;
看起来你不能像上面那样用宏来嵌套内联条件。
#define Maybe(X, Y) (X)?X->Y:nullptr
// Attempted nested usage:
return Maybe(foo, Maybe(foo->bar, baz));
// ^ VS2015 complains here saying "Expected a member name".
但是考虑到宏是多么的冗长,即使它能工作,我也看不出它比最初使用的内联条件有什么优越性。
然而,有一个模板解决方案,甚至可以绕过Mooing Duck在他的回答中指出的"双重呼叫"问题。
template<typename T_Return, typename T_X, T_Return* T_X::*Y>
T_Return* Maybe(T_X* pX)
{
return (pX) ? pX->*Y : nullptr;
}
//Usage:
// Type is required, so assume the struct Foo contains a Bar* and Bar contains a Baz*.
return Maybe<Baz, Bar, &Bar::baz>(Maybe<Bar, Foo, &Foo::bar(foo));
当然,虽然这符合要求,但它是荒谬和迟钝的。
然而,值得注意的是,有第二种类型的Null传播用于调用方法,这意味着它不需要"else"情况。它相当于:// C# code here.
if(foo != null) { foo.Bar(); }
在c#中可以:
foo?.Bar()
这是一个更容易尝试使用宏来复制的特性,并且它更"适合"普通语法。
// C++ Null Propagation
#define Maybe(X) if (auto tempX = (X)) tempX
//Usage
Maybe(GetFoo())->Bar();
//Or for setting a variable:
Maybe(GetFoo()->bar = new Bar();
它可以工作,因为它没有使用?:简写,而是使用普通的内联if语句。
请记住,这仅用于调用方法或设置成员。
如果你真的很坚持,你可以这样做:
#define ReturnMaybe(X, Y) auto tempX = X;
if (tempX != nullptr)
{
return tempX->Y;
}
else
{
return nullptr;
}
可以内联为:
#define ReturnMaybe(X, Y) auto tempX = X; if(tempX) { return tempX->Y; }else{return nullptr;}
// Usage
Bar* GetBar()
{
ReturnMaybe(GetFoo(), bar)
}
但是这不能被链接,并且考虑到几乎所有可能性的冗长性(也许除了调用方法的null传播),您将无法使用上述任何解决方案解决任何问题。
编辑-使模板答案稍微更好。
编辑2 -我和同事讨论了这个问题,想出了一些其他的选择:
#define Maybe(X) (X==nullptr) ? nullptr : X
用法:
return Maybe(foo)->bar;
有趣的是,这也适用于调用方法的情况。
Maybe(foo)->Bar();
因为它展开后的结果是有效的(尽管很奇怪)
(foo == nullptr) ?
但是,由于不能在?:简写中放入变量声明,因此无法保护return Maybe(get_next_parent())->child;
像Mooing Duck描述的那样,并且需要自己创建一个临时的
- cmake 使用 find_package 传播依赖项
- 组合"%"和可选后缀时,自动属性传播有时不起作用
- 跨目标传播库依赖项
- 为什么map有操作符[],set没有
- 张量流 c++ API 是否支持反向传播的自动微分?
- 升级到G++4.8-exception_ptr.h不支持异常传播
- 在 C++ 中将 char 转换为 int,而无需符号位传播
- 为什么需要 FPU 重置以防止 NaN 结果传播到下一个计算结果?
- 为什么'std::p mr::p olymorphic_allocator'不会在容器移动时传播?
- 如何传播 NodeJS 包装C++代码的函数故障?
- 从升压::p罗塞斯到升压::子级的信号传播
- 模拟一个函数,该函数像操作员=和破坏者一样传播到每个字段
- 如何传播未定义数量的模板参数
- 使用 std::future::unwrap 进行异常传播
- 为什么 CMake COMPILER_DEFINITIONS不传播到子目录?
- 精神 X3 无法传播可选<vector>类型的属性
- 库链接器传播:libA->libB->App 是否与 libA->App<-libB 相同
- 对象传播
- 在qtquick 1.1中传播定位的事件为背景项目
- c++中的空传播操作符