C#空合并运算符等效于C++

pftdvrlh  于 2023-04-23  发布在  C#
关注(0)|答案(9)|浏览(494)

有没有一个C++等价的C# null合并操作符?我在代码中做了太多的null检查。所以我在寻找一种方法来减少null代码的数量。

jogvjijk

jogvjijk1#

我刚发现这个The ?? operator aka the Null Coalescing Operator
你也可以在C/C++中使用?:操作符将其作为GNU扩展:

string pageTitle = getTitle() ?: "Default Title";
h5qlskok

h5qlskok2#

在C++中没有默认的方法来做到这一点,但是你可以写一个:
在C#中,??运算符定义为

a ?? b === (a != null ? a : b)

因此,C++方法看起来像

Coalesce(a, b) // put your own types in, or make a template
{
    return a != null ? a : b;
}
5rgfhyps

5rgfhyps3#

使用模板和C++11 lambdas:

template<typename TValue, typename TRhsEvaluator>
TValue coalesce(TValue lhsValue, TRhsEvaluator evaluateRhs) {
     return lhsValue ? lhsValue : evaluateRhs();
}
  • 第一个参数(左侧)只计算一次。
  • 第二个参数(右侧)仅在第一个参数为false时才求值。

请注意,if?静态地将提供的表达式转换为bool,并且指针具有内置的explicit operator bool() const运算符,该运算符等同于!= nullptr
示例用法:

void * const      nonZeroPtr = reinterpret_cast<void *>(0xF);
void * const otherNonZeroPtr = reinterpret_cast<void *>(0xA);

std::cout << coalesce(nonZeroPtr, [&] () {
    std::cout << "Side-effect. Should never be printed" << std::endl;
    return otherNonZeroPtr;
}) << std::endl;

上面的代码只会将0xf打印到控制台。
右边需要 Package 在lambda中-我们不能避免这个样板。实际上,语言应该提供一个空的合并操作符。

uidvcgyl

uidvcgyl4#

只是想通过泛化模板并添加辅助宏来扩展@Samuel Garcia的答案,以减少lambda样板:

#include <utility>

namespace coalesce_impl
{
    template<typename LHS, typename RHS>
    auto coalesce(LHS lhs, RHS rhs) ->
        typename std::remove_reference<decltype(lhs())>::type&&
    {
        auto&& initialValue = lhs();
        if (initialValue)
            return std::move(initialValue);
        else
            return std::move(rhs());
    }

    template<typename LHS, typename RHS, typename ...RHSs>
    auto coalesce(LHS lhs, RHS rhs, RHSs ...rhss) ->
        typename std::remove_reference<decltype(lhs())>::type&&
    {
        auto&& initialValue = lhs();
        if (initialValue)
            return std::move(initialValue);
        else
            return std::move(coalesce(rhs, rhss...));
    }
}

#define COALESCE(x) (::coalesce_impl::coalesce([&](){ return ( x ); }))
#define OR_ELSE     ); }, [&](){ return (

使用宏,您可以:

int* f();
int* g();
int* h();

int* x = COALESCE( f() OR_ELSE g() OR_ELSE h() );

希望这能帮上忙。

jrcvhitl

jrcvhitl5#

这里有两个宏来复制???.运算符。这些宏确保:

  • 参数只计算一次。
  • 第二个参数仅在第一个参数为null时才被计算。
  • 宏变量使用编译时删除的lambda语法在内部限定作用域。这避免了宏变量冲突。

示例用法:

COA( nullPtr, goodPtr )->sayHello();
    COA( nullPtr, COA( nullPtr, goodPtr ) )->sayHello();

    COACALL( goodPtr, sayHello() );
    COACALL( nullPtr, sayHello() );

    COACALL( COA( nullPtr, goodPtr ), sayHello() );

定义:

#define COA(a, b) ([&](){ auto val = (a); return ((val) == NULL ? (b) : (val)); }())
    #define COACALL(a, b) ([&](){ auto val = (a); if (val) (val->b); }());

注意:COACALL不返回结果。只能根据需要与void调用或alter一起使用。

vbopmzt1

vbopmzt16#

这个怎么样?

#define IFNULL(a,b) ((a) == null ? (b) : (a))
6yjfywim

6yjfywim7#

有一个GNU GCC扩展允许在缺少中间操作数的情况下使用?:运算符,请参阅省略操作数的条件。
条件表达式中的中间操作数可以省略。如果第一个操作数非零,则其值为条件表达式的值。
因此,表达式
x ? : y
如果x不为零,则其值为x;否则为y的值。
这个例子完全等同于
x ? x : y
在这种简单的情况下,省略中间操作数的能力并不是特别有用。当第一个操作数确实或可能(如果它是一个宏参数)包含副作用时,它就变得有用了。然后重复中间的操作数将执行两次副作用。省略中间操作数使用已经计算过的值,而不会产生重新计算的不良影响。
clang也支持此扩展。但是,在使用扩展之前,您应该检查您正在使用的编译器和代码的可移植性要求。值得注意的是,MSVC C++编译器不支持?:中的省略操作数。
另请参阅相关的StackOverflow讨论here

nszi6y05

nszi6y058#

只是为了补充提到?:运算符(“Elvis运算符”)的答案:我有时会使用一个helper函数和这个运算符,它获取指针或std::optional或类似类型的底层值,这些类型“ Package ”一个值并进行布尔转换以指示值的存在。例如:

template <typename T>
constexpr T coalesce (std::optional<T> opt) {
    return *opt;
}
template <typename T>
constexpr T coalesce (T fallback) {
    return fallback;
}

std::optional<int> opt1{5};
std::optional<int> opt2;
int val1 = coalesce(opt1 ?: 0);
int val2 = coalesce(opt2 ?: 0);

唯一的缺点是必须小心使用,并且不会为您提供正确使用的静态检查。例如,您可以在没有?: fallback的情况下执行coalesce(opt2),这与在没有首先检查它是否包含任何内容的情况下执行*opt2是一样的。因此,coalesce这个名称有点误导,但在我看来,如果正确使用,它看起来是不言自明的(而且相当整洁)。

xuo3flqw

xuo3flqw9#

提醒:C#合并语义

??,C#的合并运算符are的语义:

p ?? q

这里,p??运算符的左操作数,q是右操作数。p的值可以是可空类型,但q的值必须是不可空类型。如果p的值是null,则返回q的值。否则,将返回p的值。

一个C++实现

我们首先注意到C++不能使用??作为标识符;预处理器也不愿意让我们用这个名字定义一个宏。所以,让我们使用coalesce作为标识符。
我们需要以某种方式将coalesce定义为具有所需语义的中缀运算符。这可以通过运算符重载和宏的组合来实现,以便我们可以编写:

int* p = // whatever
int* q = // whatever
int* result = p coalesce q;

下面是一个实现:

#include <functional>

namespace detail {

struct coalesce_op {};

template <typename T>
struct coalesce_op_primed { 
    T&& lhs; 

    constexpr T&& operator+(T&& rhs) {
        return (lhs == nullptr) ? rhs : lhs;
    }
};

template <typename T>
constexpr coalesce_op_primed<T> operator+(T&& t, coalesce_op)
{
    return coalesce_op_primed<T>{std::forward<T>(t)};
}

} // namespace detail

#define coalesce + detail::coalesce_op{} +

GodBolt上查看它的实际操作。
备注:

  • 没有任何短路逻辑,也就是说,q * 即使不使用也会被 * 求值。可以用短路逻辑编写宏,但它不会是中缀运算符。
  • 我们不能使用GNU C扩展的?: Elvis-operator,因为它的语义是不同的。它会考虑一个非空的false值作为选择RHS操作数的原因,即false ?: 123将产生123,而false ?? 123false使用C#合并语义。
  • 我并没有尝试在std::optional上实现这个功能,检测nullopt。如果需要的话,也可以使用一些if-constexpr或tagged-dispatch TMP。

相关问题