有時候,在搭配 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;

兩相比較起來,有時候這是值得的。


  1. 先不論 cout 的型別 std::ostream 其實是 typedef 自 basic_ostream<char>,這點與這個問題無關。
  2. ISO+IEC+14882-1998