Posted on May 4th, 2009 at 1:05 by fr3@K
上週看到了一篇引起我興趣的 blog - C++ Format String using boost::format. 冒著再次被人指為 屁的半死 的風險, 身為男子漢的我, 還是決定把心中的 替代解法 寫給了 Hubert 參考. Hubert 建議用 Boost.Format 的解法很好, 可我就是就是忍不住貪玩啊.
緣由交代完了. 有興趣看給 Hubert 參考的解法的朋友請自便, 因為接下來要談的不是它1, 而是另一個沒那麼漂亮, 但卻挺有趣的解法.
相信很多人都有這樣的需求 - 把 object 轉成 human readable string, 或許經過簡單的 formatting. 我一開始想到的就是:
-
// Assuming `foo` is an integer.
-
ostringstream oss;
-
oss <<"The value of foo is: " <<foo;
-
string output = oss.str();
-
ostringstream oss;
-
string output =
-
(oss <<"The value of foo is: " <<foo).str();
-
template <class OutputStream, class T>
-
OutputStream& operator<<(OutputStream& os, const T& x)
-
{
-
ostream& base = os;
-
base <<x;
-
return os;
-
}
- 這個 template 會影響所有 user-defined type 的
operator<<, 原本沒問題的 code 在有了這個 template 後可能無法 compile 或正常運作. - 當 template parameter
OutputStream已是ostream時, 這個 template inserter (inserter template 實例化所產生的實體) 會沒完沒了的 recursive 自己 call 自己, 會在 run time 發作直到把 stack 吃光為止. -
template <class OutputStream, class T>
-
typename boost::disable_if<
-
boost::mpl::or_<
-
boost::mpl::not_<
-
boost::is_base_of<ostream, OutputStream>>,
-
boost::is_same<OutputStream, ostream>>,
-
OutputStream>::type& operator<<(
-
OutputStream& os, const T& x)
-
{
-
ostream& base = os;
-
base <<x;
-
return os;
-
}
ostream不是 (boost::not_)OutputStream的 base class (boost::is_base_of).2OutputStream就是 (boost::is_same)ostream.
雖然這完全是能達到我的目的, 但也正如 Hubert 說的:
總覺得透過 operator += 以及 stringstream 等東西,並沒有辦法像 sprintf 那樣直覺而優雅的將變數置換至字串當中。
那把對 oss 做 insertion 的 code 跟對其呼叫 str() member function 的 code 寫在一起會不會稍微優雅些? 把 Listing 1 的三個 statement 縮減成兩個:
的確就稍微好那麼一些些 ><. 如果這段 code snip 可以 compile 的話.
對 ostringstream 的 instance (actually lvalue) 做 insertion 呼叫到的實際上是它的 base class - ostream 的 inserter. 這些 inserter 的 return value 當然也是 ostream&. 換句話說, 對 ostream 的 derived class 的 instance 做 insertion, 在沒有特殊的 overload 的狀況下, return value 都不會是 derived class 的 reference. 也因此無法對這些 return value 呼叫 derived class 的特異功能; 例如 ostringstream::str() - 把 insert 到 ostringstream 的 buffer 的內容轉成 string 傳回來.
在我看來, Listing 2 雖不合法, 但會是好用的語法, 可以把 code 寫的更乾淨利落. 於是我考慮了下面的 inserter template:
由於要能夠給各種繼承自 ostream 的 output stream 使用, 因此第一個參數 - OutputStream - 的 type 得是個 template parameter, 才能讓 compiler 認為這個 template 是比 base class (ostream) 的 inserter 更好的選擇. 但也因此引發兩個使它無法被使用的 side effect:
也考慮過換個 operator 來實現這樣的功能, 例如 operator%, 但那依然無法解決上述的第一個 side effect, 只是換個 operator 出問題. 於是還是請出了 Boost:
Note: 由於不明原因, 我用的 code highlighter plugin 會把某些空格省略掉. 這通常不會造成明顯的問題, 結果依然是合法的 code. 但在 Listing 4 的 line 5 and 6 的結尾, 這個 plugin 把應該是 > > (中間有 space) 的 code 顯示成在現行標準下不合法的連續兩個 >. 請特別注意.
宣告這個 inserter template 的 Listing 4 可以這樣解讀; 不讓 compiler 考慮 (boost::disable_if) 採用這個 template (同時也不會有具體實現這個 template 的問題), 如果下列某個條件成立 (boost::or_):
這段 code 既繞舌又不好寫. 連屁的半死的我也寫了超過十分鐘才寫對. 不過結果看起來不錯, 保留了output stream 做 insertion 之前的型別, 也在沒有明顯的 side effect 的情況之下讓類似 Listing 2 的操作合法了.
Full source code: ostream_type_preserve.cpp
![]() |
|
| Previous Post « Contemplation of Prejudice « |
Next Post » NoScript Gone Bad!? WTF! » |
3 Comments »
再補充一下,用這種方法,我弄了從 0 個參數到 20 個參數的版本,所以你可以寫
cout << MyFormat(”no value” ) << endl;
cout << MyFormat(”The value of foo is: %d”, foo) << endl;
cout << MyFormat(”x = %d, y = %f z = 0x%x: %d”, 1, 2.5, 0xff) << endl;
cout << MyFormat(”%s say: %s because he has only %d dollors”, “simon”, str, szMoney ) << endl;
20 個參數應該很夠用了.
Comment by av — May 4, 2009 @ 22:49
Hi av,
你的方法很實用, 最明顯的優點就是保留了 printf 的 format 方式與 operator<< 的習慣.
我想是文內沒清楚表達, 這篇 post 想呈現重點的其實是在 insertion 時保留 output stream 的型別. 這樣可能可以有一些不只是字串化與格式化的有趣應用. 雖然這個 idea 是從 formatting 那邊發想來的.
譬如… 假設在操作一個繼承自 iostream 的 TCP stream, 就能夠在 insert (send) 一個 request 後, 直接 extract (recv) response:
request req(...); response rsp; tcpstream << req >> rsp;
或許該把這個我認為有趣且可能有用的例子加到 post 裏面.
Except where otherwise noted, COdE fr3@K by
fr3@K is licensed under a
Creative Commons Attribution-Share Alike 3.0 License.
27 queries. 0.508 seconds.
powered by wordpress 2.8.6 | theme by tony








我自己是用 boost::format 加上一”些” wrappers. 類似這樣:
template <class T1> inline std::string MyFormat(const char *Format, const T1& t1) { return ( boost::format(Format) % t1 ).str(); } template <class T1, class T2> inline std::string MyFormat(const char *Format, const T1& t1, const T2& t2) { return ( boost::format(Format) % t1 % t2 ).str(); }就這樣一大堆 overloaded function 來對應不同數量的參數,好處是不需要用 % 這個符號,用起來比較符合原來的習慣:
cout << MyFormat("The value of foo is: %d", foo) << endl; ed: code snips formatted - fr3@KComment by av — May 4, 2009 @ 22:38