標籤:

為什麼C++的逗號運算符是可以重載的?

是什麼因素使得運算符重載在C++中引入時把逗號運算符也作為可重載的運算符?


@邱昊宇 的回答已經講得很清楚了, 我舉個重載逗號運算符的應用

Folly:Expected 是一個類似於 Optional 的類型,不過它存儲的是值或者錯誤.

它可以用一串then來鏈式調用若干各函數,順便還能檢查參數類型是否匹配.

{
Expected& ex =
Expected&, E&>{in_place, new int(42)}.then(
[](std::unique_ptr& p) { return makeExpected&(*p); },
[](int i) { return i == 42 ? "yes" : "no"; });
EXPECT_TRUE(bool(ex));
EXPECT_EQ("yes", *ex);
}

Expected& ex= Expected&{233};
ex.then([](int) {})
.then([](Unit) { std::cout &<&< "hello "; }) .then([](Unit) { std::cout &<&< "world "; });

void類型是沒有值, 於是我們用Unit來替代它(其實就是空的struct)

每一個then其實是返回一個Folly::Expected, 裡面存儲著對應類型的值或者錯誤. then的調用會轉發給內部的then_

template &< class This, class Fn, class... Fns, class E = ExpectedErrorType&,
class T = ExpectedHelper&>
static auto then_(This ex, Fn fn, Fns... fns) -&> decltype(T::then_(
T::template return_&(
(std::declval&()(std::declval&().value()), unit)),
std::declval&()...)) {
if (LIKELY(ex.which_ == expected_detail::Which::eValue))
return T::then_(
T::template return_&(
// Uses the comma operator defined above IFF the lambda
// returns non-void.
(static_cast&(fn)(static_cast&(ex).value()), unit)),
static_cast&(fns)...);
return makeUnexpected(static_cast&(ex).error());
}

如果then接受的函數返回類型是void,那麼我們需要把它轉換成Unit類型. 這種情況下

(static_cast&(fn)(static_cast&(ex).value()), unit)

中的逗號運算符是沒重載的版本,值是第二個操作數.

如果then接受函數的返回類型不是void,會使用重載的逗號運算符,返回第一個操作數的值.

namespace expected_detail_ExpectedHelper {
// Tricky hack so that Expected::then can handle lambdas that return void
template &
inline T operator,(T t, Unit) noexcept {
return static_cast&(t);
}


因為有時候真的有用啊。opencv中有下面的矩陣初始化方法:

Mat C = (Mat_&(3,3) &<&< 0, -1, 0, -1, 5, -1, 0, -1, 0);

我頭一次看到的時候百思不得其解,後來特意翻了翻源碼才發現是重載了逗號運算符。


補充下 @邱昊宇 提到的微小的工作。

Qt中的信號槽,在同步執行時,槽函數的返回值直接由信號函數返回。信號函數可以是void返回值,也可以是和槽函數相同的返回值。

在源碼實現里,有返回值的信號函數和返回void的信號函數是同一個流程。而當槽函數的返回值傳遞過去時,就是用重載逗號運算符實現的。

// 執行槽函數的代碼
template & struct FunctorCall;
template &
struct FunctorCall&, List&, R, Function&> {
static void call(Function f, void **arg) {
f((*reinterpret_cast&::Type *&>(arg[II+1]))...), ApplyReturnValue&(arg[0]);
}
};
template &
struct FunctorCall&, List&, R, SlotRet (Obj::*)(SlotArgs...)&> {
static void call(SlotRet (Obj::*f)(SlotArgs...), Obj *o, void **arg) {
(o-&>*f)((*reinterpret_cast&::Type *&>(arg[II+1]))...), ApplyReturnValue&(arg[0]);
}
};
template &
struct FunctorCall&, List&, R, SlotRet (Obj::*)(SlotArgs...) const&> {
static void call(SlotRet (Obj::*f)(SlotArgs...) const, Obj *o, void **arg) {
(o-&>*f)((*reinterpret_cast&::Type *&>(arg[II+1]))...), ApplyReturnValue&(arg[0]);
}
};

// 逗號運算符封裝返回值
/*
trick to set the return value of a slot that works even if the signal or the slot returns void
to be used like function(), ApplyReturnValue&(return_value)
if function() returns a value, the operator,(T, ApplyReturnValue&) is called, but if it
returns void, the builtin one is used without an error.
*/
template &
struct ApplyReturnValue {
void *data;
explicit ApplyReturnValue(void *data_) : data(data_) {}
};
template&
void operator,(const T value, const ApplyReturnValue& container) {
if (container.data)
*reinterpret_cast&(container.data) = value;
}
template&
void operator,(T value, const ApplyReturnValue& container) {
if (container.data)
*reinterpret_cast&(container.data) = value;
}
template&
void operator,(T, const ApplyReturnValue& ) {}

這裡,通過重載逗號運算符,加上模板偏特化,實現了這個返回值自動封裝操作


允許重載逗號運算符的原因,The Design and Evolution of C++ 里是這麼說的:

11.5.5 Overloading the Comma Operator
At the urging of Margaret Ellis, I allowed overloading of the comma operator. Basically, I couldn"t find any reason not to at the time. Actually, there is a reason: a, b is already defined for any a and b, so allowing overloading enables the programmer to change the meaning of a built-in operator. Fortunately, that is only possible if either a or b is a class object. There appear to be few practical uses of operator,(). Accepting it was primarily a generalization.

大致就是

  • 有人催
  • 反正重載一下也不會懷孕
  • 而且真的有人利用重載逗號運算符做了點(微小的)工作

p.s. 這一章開頭也有提到,在重載運算符這方面「只要重載有意義、不與運算符本身的語義相悖,目標是允許重載所有操作符」。


真的假的?我試試


記得DE裡面有說,因為其他雙目運算符都是能重載的,為了完整性就讓逗號運算符也可以重載了


啥,逗號可以重載?


只要是語法分析階段,能確定的,都不會影響效率,頂多編譯慢一些。只要,別學py,玩動態多態性就好。


推薦閱讀:

模擬示波器通常是什麼樣的架構什麼系統,如何編程(有人在日立V-422上編寫了一款FPS遊戲)?
有什麼好方法能把高數和編程聯繫起來么?
Android 應用中半透明狀態欄的原理是什麼?(有分歧故來提問)
碼農需要的基本款電腦是怎樣的配置?
C語言標準中為什麼不規定int類型的具體長度?

TAG:編程 | C |