Overloading C++ conditional operator for mixing types
有時候,在搭配 conditional operator (?:) 輸出資料時,我們會依據資料的值,來決定輸出什麼東西。例如:
#include <iostream>
using namespace ::std;
int main()
{
int delay_time = -1; // negative for "random" delay time.
cout << "delay: ";
if (delay_time < 0) {
cout << "random";
}
else {
cout << delay_time;
}
cout << endl;
return 0;
}
但這樣寫實在有些難看,所以我們可能會想要使用 conditional operator,也就是 ?:,讓程式看起來優雅一些:
#include <iostream>
using namespace ::std;
int main()
{
int delay_time = -1; // negative for "random" delay time.
cout << "delay: " << ((delay_time < 0) ? "random" : delay_time) << endl;
return 0;
}
可是,如果程式這個樣子寫,卻會遭受到 compiler 的無情攻擊:
mix_type_cond_op.cpp: In function `int main()': mix_type_cond_op.cpp:8: error: operands to ?: have different types
這主要是因為,conditional operator 是個可以被 inline 的 expression。然而,因為使用 << 將這個 expression 輸出到 cout 時,實際上是靠 overloading 依據右邊的 expression 的型別,以決定究竟要呼叫哪一個 function 將 right-hand-side expression 輸出。由於 overload resolution 是在 compile-time 完成,但 conditional operator 卻必須要等到 run-time,才能知道要 evaluate 後面哪一個部份,因此,conditional operator「必須」要讓後面的 true/false parts 的型別一樣,否則便不可能在 compile-time 決定該怎麼處理 overloading[1]。
那能不能靠 overload conditional operator 本身,來解決這個問題呢?例如:
template <class ResultT, class TrueT, class FalseT>
ResultT& operator ?: (bool condition, TrueT& true_part, FalseT& false_part)
{
if (condition) {
return true_part;
}
else {
return false_part;
}
}
可惜,這個 template function 是錯的,因為如同上面的原因一樣,compiler 還是無從得知 ResultT 到底是 TrueT 還是 FalseT。事實上,就算不是用 template 來寫這個 operator overloading,還是不可能寫出來。因此,在 C++98[2] 13.5.3 節裡便規定了,conditional operator (?:) 不能夠被 overload:
The following operators cannot be overloaded:
. .* :: ?:nor can the preprocessor symbols
#and##(clause 16).
不過我想要優雅地寫程式啊!所以只好放棄 overload conditional operator,改用類似一般的 manipulator 的方法處理。
首先,我們先寫一個 helper manipulator 叫 cond_op_sel,負責「承載」conditional operator 的三個 operands:condition (cond)、true_part (tp) 與 false_part (fp)。
/**
* Simulate and enhance the behavior of build-in conditional operator (?:).
*
* Typically, a conditional operator accepts three operands: condition,
* true_part, and false_part.
*/
template <class TrueT, class FalseT>
class cond_op_sel
{
public:
/**
* @param[in] cond condition of the conditional operation
* @param[in] tp true part of the conditional operation
* @param[in] fp false part of the conditional operation
*/
cond_op_sel(bool cond, TrueT& tp, FalseT& fp)
: cond_(cond), tp_(tp), fp_(fp)
{}
bool condition() const { return cond_; }
const TrueT& true_part() const { return tp_; }
TrueT& true_part() { return tp_; }
const FalseT& false_part() const { return fp_; }
FalseT& false_part() { return fp_; }
private:
bool cond_; //<! result of the condition
TrueT& tp_; //<! reference to the true part passed in
FalseT& fp_; //<! reference to the false part passed in
};
/**
* Helper function for creating cond_op_sel<> object.
*/
template <class TrueT, class FalseT>
cond_op_sel<TrueT, FalseT> cond_op(bool cond, TrueT& tp, FalseT& fp)
{
return cond_op_sel<TrueT, FalseT>(cond, tp, fp);
}
然後針對 operator << 寫一個 overloading function,實際應用 cond_op_sel<>。
template <class TrueT, class FalseT>
ostream& operator << (ostream& os, const cond_op_sel<TrueT, FalseT>& cos)
{
if (cos.condition()) {
os << cos.true_part();
}
else {
os << cos.false_part();
}
return os;
}
這樣一來,就可以把醜陋的 if-then-else 藏到 operator<<() 裡,寫出如下的程式碼片段:
void test(bool cond)
{
int i = 3;
string s = "str";
cout << "cond: " << cond << endl
<< "i: " << i << endl
<< "s: " << s << endl;
cout << "i or s by cond: " << cond_op(cond, i, s) << endl;
}
int main()
{
test(true);
test(false);
return 0;
}
執行結果如下:
cond: 1 i: 3 s: str i or s by cond: 3 cond: 0 i: 3 s: str i or s by cond: str
實際上,這個作法只是依靠 cond_op_sel<> 暫時把 condition 與 true/false part 存起來,留待 operator << () 時才使用,所以實際上運行在 run-time,並沒有可能把 run-time 的 overhead,挪到 compile-time 來,甚至可能增加了額外的 overhead。不過,由於語法的轉化,程式的可讀性便可以增加,例如:
cout << "Value: " << cond_op(pValue, *pValue, "(n/a)") << endl;
兩相比較起來,有時候這是值得的。
2 Comments
在編譯時期若知道 cond的值,應該可以利用 partial spacialization 的特性來撰寫這個動作,如下:
#include
#include
using namespace std;
struct TrueTOrFalseT{};
template
struct dummy_traits{
typedef Type T;
};
template
struct cond_p{
typename dummy_traits::T
operator()( const TrueT &ttp, const FalseT &ft );
};
template
struct cond_p{
typename dummy_traits::T
operator()( const TrueT &tt, const FalseT &ft )
{
return tt;
}
};
template
struct cond_p{
typename dummy_traits::T
operator()( const TrueT &tt, const FalseT &ft )
{
return ft;
}
};
int main()
{
cout()( "Allen", 10 ) ()( "Allen", 10 )
上一篇的程式碼好像亂掉了...
http://rafb.net/p/lQh1lJ41.html
因為 function template 沒有提供 partial specialization,所以我想不到好方法把語法作的簡潔。
不過...conditional opeator作成編譯時運算似乎沒什麼好處...:P
Post a Comment