Boost.Lexical-Cast Alternatives
Posted on September 17th, 2009 at 21:49 by fr3@K

今天稍早, 跟一位同事聊到 Boost 的 lexical_cast. 打開了 Firefox 請出 Google, 想找到能幫助我介紹 lexical_cast 的資源. 意外地, Google 把我帶到讓我賺了介紹獎金, 現在跟我是同事的 Jeff, 談到 lexical_cast 的 文章.

在這篇文字的討論串, Jeff 寫道:

我覺得如果可以用這樣的寫法,那就太好了:

uint32_t ID = lexical_cast<auto>("1234567");

甚至最好是:

template <class From, class To = auto>
To lexical_cast(From s) {...}

uint32_t ID = lexical_cast("1234567");

Jeff Hung


小弟既然收了 Jeff 的好處, 當然得回報一下試著幫忙想想辦法:1

C++:
  1. #include <sstream>
  2. #include <iostream>
  3. #include <stdexcept>
  4.  
  5. class lexi_cast
  6. {
  7. public:
  8.   template <class Input>
  9.   lexi_cast(const Input& in)
  10.   {
  11.     ss_ <<in;
  12.     if(!ss_.good())
  13.       throw std::runtime_error("lexi_cast failed");
  14.   }
  15.  
  16.   template <class Output>
  17.   operator Output()
  18.   {
  19.     Output out;
  20.     ss_>> out;
  21.     if(ss_.bad() || ss_.fail())
  22.       throw std::runtime_error("lexi_cast failed");
  23.  
  24.     return out;
  25.   }
  26.  
  27. private:
  28.   std::stringstream ss_;
  29. };
  30.  
  31. int main()
  32. {
  33.   using namespace std;
  34.  
  35.   // Converts C-style string to int.
  36.   int i = lexi_cast("123");
  37.   cout <<i <<endl;
  38.  
  39.   // Converts int to std::string.
  40.   string s = lexi_cast(i);
  41.   cout <<s <<endl;
  42.  
  43.   return 0;
  44. }

    岔題一下.

    我一直不是很認同, 在 C++ 中, 以不同的規則來命名 class/function/object. 主要的原因是, 我認為這三者的分野其實並沒有那麼絕對.

    上面的 lexi_cast 就是一個很好的例子. 雖然它與 "真正" 的 function 相較仍有細微的不同, 但它的確是設計來讓使用者以 "function 風格" 使用的. 硬是把它的名字取得跟其他 function 格格不入, 豈不是很礙眼? 一個 function-like 的 instance (function instance, class instance or object instance) 可以在一個版本是真的 function, 在下一個版本卻變成 functional object, 甚至是如例中的 class instance.

    從另一個方面來看, 如果一個 "正常" class 設計的讓人容易把該 class 本身或所 instantiate 的 object 錯誤地當作 function 使用, 而必須以命名原則來區分, 其設計者更是應該要好好反省.

看起來這似乎已滿足了 Jeff 的基本需求, 只是這全自動的轉換機制也有它的問題. 有些時候, 不提供更多資訊給 compiler, 就是會有 ambiguity. 例如 stream insertion:

C++:
  1. // Ambigous! This won't compile!
  2.   cout <<lexi_cast(some_object);

Output stream 的 operator<<很多 overload. 由於 lexi_cast 提供了 generic 的 conversion operator, 因此 compiler 會找到多個可行 (viable) 的 overload. 若我們不顯性提示, compiler 會無法決定到底該採用哪個 overload. 這時候, lexi_cast 的全自動 (或說是缺乏手動指定的) 轉換功能反而變成了一種缺陷.

Attempt number 2. 簡單的把 lexi_cast 改成一個可以指定目的形別的 class template, 並配上做自動轉換的 default template parameter (struct auto_deduce) 以及 specialization:

C++:
  1. struct auto_deduce {};
  2.  
  3. template <class Output = auto_deduce>
  4. class lexi_cast2
  5. {
  6. public:
  7.   template <class Input>
  8.   lexi_cast2(const Input& in)
  9.   {
  10.     ss_ <<in;
  11.     if(!ss_.good())
  12.       throw std::runtime_error("lexi_cast2 failed");
  13.   }
  14.  
  15.   operator Output()
  16.   {
  17.     Output out;
  18.     ss_>> out;
  19.     if(ss_.bad() || ss_.fail())
  20.       throw std::runtime_error("lexi_cast2 failed");
  21.  
  22.     return out;
  23.   }
  24.  
  25. private:
  26.   std::stringstream ss_;
  27. };
  28.  
  29. template <>
  30. class lexi_cast2</*Output =*/auto_deduce>
  31. {
  32. public:
  33.   template <class Input>
  34.   lexi_cast2(const Input& in)
  35.   {
  36.     ss_ <<in;
  37.     if(!ss_.good())
  38.       throw std::runtime_error("lexi_cast2 failed");
  39.   }
  40.  
  41.   template <class Output>
  42.   operator Output()
  43.   {
  44.     Output out;
  45.     ss_>> out;
  46.     if(ss_.bad() || ss_.fail())
  47.       throw std::runtime_error("lexi_cast2 failed");
  48.  
  49.     return out;
  50.   }
  51.  
  52. private:
  53.   std::stringstream ss_;
  54. };

效果並不理想. 自動轉換 (default behavior of class template lexi_cast2) 雖不需要指定 type, 但還是需要角括號 (angled bracket), 用起來醜到爆:

C++:
  1. int main()
  2. {
  3.   using namespace std;
  4.  
  5.   // Note the next (commented) line won't compile,
  6.   // int i = lexi_cast2("123");
  7.  
  8.   // Angled brackets needed for auto deduction.
  9.   // Not pretty at all.
  10.   int i = lexi_cast2<>("123");
  11.   cout <<i <<endl;
  12.  
  13.   string s = lexi_cast2<>(i);
  14.   cout <<s <<endl;
  15.  
  16.   // Now we could specify which "type" to convert to.
  17.   cout <<lexi_cast2<int>(s) <<endl;
  18.  
  19.   return 0;
  20. }

Attempt number 3. 下面的程式中的 class template lexi_cast3_helper 與 specialization 是從前面的 lexi_cast2 修改過來的. 只增加了 copy-ctor:2

C++:
  1. template <class Output>
  2. class lexi_cast3_helper
  3. {
  4. public:
  5.   template <class Input>
  6.   lexi_cast3_helper(const Input& in)
  7.   {
  8.     ss_ <<in;
  9.     if(!ss_.good())
  10.       throw std::runtime_error("lexi_cast3 failed");
  11.   }
  12.   // Copy-ctor. Needed for compilation, but not
  13.   // invoked at run-time.
  14.   lexi_cast3_helper(const lexi_cast3_helper& other)
  15.   {
  16.     ss_ <<other.ss_.str();
  17.   }
  18.  
  19.   operator Output()
  20.   {
  21.     Output out;
  22.     ss_>> out;
  23.     if(ss_.bad() || ss_.fail())
  24.       throw std::runtime_error("lexi_cast3 failed");
  25.  
  26.     return out;
  27.   }
  28.  
  29. private:
  30.   std::stringstream ss_;
  31. };
  32.  
  33. template <>
  34. class lexi_cast3_helper</*Output =*/auto_deduce>
  35. {
  36. public:
  37.   template <class Input>
  38.   lexi_cast3_helper(const Input& in)
  39.   {
  40.     ss_ <<in;
  41.     if(!ss_.good())
  42.       throw std::runtime_error("lexi_cast3 failed");
  43.   }
  44.   // Copy-ctor. Needed for compilation, but not
  45.   // invoked at run-time.
  46.   lexi_cast3_helper(const lexi_cast3_helper& other)
  47.   {
  48.     ss_ <<other.ss_.str();
  49.   }
  50.  
  51.   template <class Output>
  52.   operator Output()
  53.   {
  54.     Output out;
  55.     ss_>> out;
  56.     if(ss_.bad() || ss_.fail())
  57.       throw std::runtime_error("lexi_cast3 failed");
  58.  
  59.     return out;
  60.   }
  61.  
  62. private:
  63.   std::stringstream ss_;
  64. };

lexi_cast3_helper 搭配上兩個 function template - 兩個 lexi_cast3 的 overload. 一個用來在自動轉換時躲避角括號 (angled bracket), 另一個讓使用者自行指定轉換的目的形別:

C++:
  1. // Overload for output type auto deduction.
  2. template <class Input>
  3. lexi_cast3_helper<auto_deduce>
  4.   lexi_cast3(const Input& in)
  5. {
  6.   return lexi_cast3_helper<auto_deduce>(in);
  7. }
  8. // Overload for user-specified output type.
  9. template <class Output, class Input>
  10. lexi_cast3_helper<Output>
  11.   lexi_cast3(const Input& in)
  12. {
  13.   return lexi_cast3_helper<Output>(in);
  14. }

結果是:

C++:
  1. int main()
  2. {
  3.   using namespace std;
  4.  
  5.   // No ugly angled brackets for
  6.   // auto deduction of output type.
  7.   int i = lexi_cast3("123");
  8.   cout <<i <<endl;
  9.  
  10.   string s = lexi_cast3(i);
  11.   cout <<s <<endl;
  12.  
  13.   // User-specified output type.
  14.   cout <<lexi_cast3<int>(s) <<endl;
  15.  
  16.   return 0;
  17. }

這次看來就像樣多了. 能美美地做自動轉換, 也能自行指定轉換的目的形別.

The moral of this post - 果然還是 C++ 好玩!

[Update. Sep 22, 2009]

Improved and simplified. Per Jeff's request:

C++:
  1. class lexi_cast4_helper
  2. {
  3. public:
  4.   template <class Input>
  5.   lexi_cast4_helper(const Input& in)
  6.   {
  7.     ss_ <<in;
  8.     if(!ss_.good())
  9.       throw std::runtime_error("lexi_cast4 failed");
  10.   }
  11.   // Copy-ctor. Needed for compilation, but not
  12.   // invoked at run-time.
  13.   lexi_cast4_helper(const lexi_cast4_helper& other)
  14.   {
  15.     ss_ <<other.ss_.str();
  16.   }
  17.  
  18.   template <class Output>
  19.   operator Output()
  20.   {
  21.     Output out;
  22.     ss_>> out;
  23.     if(ss_.bad() || ss_.fail())
  24.       throw std::runtime_error("lexi_cast4 failed");
  25.  
  26.     return out;
  27.   }
  28.  
  29. private:
  30.   std::stringstream ss_;
  31. };
  32.  
  33. // Overload for output type auto deduction.
  34. template <class Input>
  35. lexi_cast4_helper lexi_cast4(const Input& in)
  36. {
  37.   return lexi_cast4_helper(in);
  38. }
  39. // Overload for user-specified output type.
  40. // Instead of returning conversion helper,
  41. // this version returns output type.
  42. template <class Output, class Input>
  43. Output lexi_cast4(const Input& in)
  44. {
  45.   return lexi_cast4_helper(in);
  46. }

  1. Sorry, Jeff. 明明是我自己愛玩卻還要遷拖到你. []
  2. 由於 IOStream 是不能 copy 的, 因此 lexi_cast3_helper 需要有 copy-ctor 才能 compile. 有趣的是, 因為 RVO 的關係, 這些 copy-ctor 根本沒有 run-time 的用處, 不會被執行到. 可以把它的內容 comment 掉, 甚至亂改, 只要能 compile & link, 仍舊會正常運作. []
del.icio.us:Boost.Lexical-Cast Alternatives digg:Boost.Lexical-Cast Alternatives spurl:Boost.Lexical-Cast Alternatives newsvine:Boost.Lexical-Cast Alternatives furl:Boost.Lexical-Cast Alternatives Y!:Boost.Lexical-Cast Alternatives 黑米共享書籤:Boost.Lexical-Cast Alternatives 推推王:Boost.Lexical-Cast Alternatives
Previous Post
« Server side connection/session lingering «
Next Post
» So, You Think C++ is Slower than C »

2 Comments »

Comment #9641

The lexi_cast3() failed to compile for my simple test case:

CHECK(lexi_cast3<string>(123) == "123");

(CHECK() is a macro of my tiny test framework.)

Comment by jeffhung — September 21, 2009 @ 23:29


Comment #9717

Works with lexi_cast4 :) Have fun~

Comment by fr3@K — September 22, 2009 @ 10:45


Comments RSS TrackBack URI

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>