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
-
#include <sstream>
-
#include <iostream>
-
#include <stdexcept>
-
-
class lexi_cast
-
{
-
public:
-
template <class Input>
-
lexi_cast(const Input& in)
-
{
-
ss_ <<in;
-
if(!ss_.good())
-
throw std::runtime_error("lexi_cast failed");
-
}
-
-
template <class Output>
-
operator Output()
-
{
-
Output out;
-
ss_>> out;
-
if(ss_.bad() || ss_.fail())
-
throw std::runtime_error("lexi_cast failed");
-
-
return out;
-
}
-
-
private:
-
std::stringstream ss_;
-
};
-
-
int main()
-
{
-
using namespace std;
-
-
// Converts C-style string to int.
-
int i = lexi_cast("123");
-
cout <<i <<endl;
-
-
// Converts int to std::string.
-
string s = lexi_cast(i);
-
cout <<s <<endl;
-
-
return 0;
-
}
- 岔題一下.
我一直不是很認同, 在 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:
-
// Ambigous! This won't compile!
-
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:
-
struct auto_deduce {};
-
-
template <class Output = auto_deduce>
-
class lexi_cast2
-
{
-
public:
-
template <class Input>
-
lexi_cast2(const Input& in)
-
{
-
ss_ <<in;
-
if(!ss_.good())
-
throw std::runtime_error("lexi_cast2 failed");
-
}
-
-
operator Output()
-
{
-
Output out;
-
ss_>> out;
-
if(ss_.bad() || ss_.fail())
-
throw std::runtime_error("lexi_cast2 failed");
-
-
return out;
-
}
-
-
private:
-
std::stringstream ss_;
-
};
-
-
template <>
-
class lexi_cast2</*Output =*/auto_deduce>
-
{
-
public:
-
template <class Input>
-
lexi_cast2(const Input& in)
-
{
-
ss_ <<in;
-
if(!ss_.good())
-
throw std::runtime_error("lexi_cast2 failed");
-
}
-
-
template <class Output>
-
operator Output()
-
{
-
Output out;
-
ss_>> out;
-
if(ss_.bad() || ss_.fail())
-
throw std::runtime_error("lexi_cast2 failed");
-
-
return out;
-
}
-
-
private:
-
std::stringstream ss_;
-
};
效果並不理想. 自動轉換 (default behavior of class template lexi_cast2) 雖不需要指定 type, 但還是需要角括號 (angled bracket), 用起來醜到爆:
-
int main()
-
{
-
using namespace std;
-
-
// Note the next (commented) line won't compile,
-
// int i = lexi_cast2("123");
-
-
// Angled brackets needed for auto deduction.
-
// Not pretty at all.
-
int i = lexi_cast2<>("123");
-
cout <<i <<endl;
-
-
string s = lexi_cast2<>(i);
-
cout <<s <<endl;
-
-
// Now we could specify which "type" to convert to.
-
cout <<lexi_cast2<int>(s) <<endl;
-
-
return 0;
-
}
Attempt number 3. 下面的程式中的 class template lexi_cast3_helper 與 specialization 是從前面的 lexi_cast2 修改過來的. 只增加了 copy-ctor:2
-
template <class Output>
-
class lexi_cast3_helper
-
{
-
public:
-
template <class Input>
-
lexi_cast3_helper(const Input& in)
-
{
-
ss_ <<in;
-
if(!ss_.good())
-
throw std::runtime_error("lexi_cast3 failed");
-
}
-
// Copy-ctor. Needed for compilation, but not
-
// invoked at run-time.
-
lexi_cast3_helper(const lexi_cast3_helper& other)
-
{
-
ss_ <<other.ss_.str();
-
}
-
-
operator Output()
-
{
-
Output out;
-
ss_>> out;
-
if(ss_.bad() || ss_.fail())
-
throw std::runtime_error("lexi_cast3 failed");
-
-
return out;
-
}
-
-
private:
-
std::stringstream ss_;
-
};
-
-
template <>
-
class lexi_cast3_helper</*Output =*/auto_deduce>
-
{
-
public:
-
template <class Input>
-
lexi_cast3_helper(const Input& in)
-
{
-
ss_ <<in;
-
if(!ss_.good())
-
throw std::runtime_error("lexi_cast3 failed");
-
}
-
// Copy-ctor. Needed for compilation, but not
-
// invoked at run-time.
-
lexi_cast3_helper(const lexi_cast3_helper& other)
-
{
-
ss_ <<other.ss_.str();
-
}
-
-
template <class Output>
-
operator Output()
-
{
-
Output out;
-
ss_>> out;
-
if(ss_.bad() || ss_.fail())
-
throw std::runtime_error("lexi_cast3 failed");
-
-
return out;
-
}
-
-
private:
-
std::stringstream ss_;
-
};
把 lexi_cast3_helper 搭配上兩個 function template - 兩個 lexi_cast3 的 overload. 一個用來在自動轉換時躲避角括號 (angled bracket), 另一個讓使用者自行指定轉換的目的形別:
-
// Overload for output type auto deduction.
-
template <class Input>
-
lexi_cast3_helper<auto_deduce>
-
lexi_cast3(const Input& in)
-
{
-
return lexi_cast3_helper<auto_deduce>(in);
-
}
-
// Overload for user-specified output type.
-
template <class Output, class Input>
-
lexi_cast3_helper<Output>
-
lexi_cast3(const Input& in)
-
{
-
return lexi_cast3_helper<Output>(in);
-
}
結果是:
-
int main()
-
{
-
using namespace std;
-
-
// No ugly angled brackets for
-
// auto deduction of output type.
-
int i = lexi_cast3("123");
-
cout <<i <<endl;
-
-
string s = lexi_cast3(i);
-
cout <<s <<endl;
-
-
// User-specified output type.
-
cout <<lexi_cast3<int>(s) <<endl;
-
-
return 0;
-
}
這次看來就像樣多了. 能美美地做自動轉換, 也能自行指定轉換的目的形別.
The moral of this post - 果然還是 C++ 好玩!
[Update. Sep 22, 2009]
Improved and simplified. Per Jeff's request:
-
class lexi_cast4_helper
-
{
-
public:
-
template <class Input>
-
lexi_cast4_helper(const Input& in)
-
{
-
ss_ <<in;
-
if(!ss_.good())
-
throw std::runtime_error("lexi_cast4 failed");
-
}
-
// Copy-ctor. Needed for compilation, but not
-
// invoked at run-time.
-
lexi_cast4_helper(const lexi_cast4_helper& other)
-
{
-
ss_ <<other.ss_.str();
-
}
-
-
template <class Output>
-
operator Output()
-
{
-
Output out;
-
ss_>> out;
-
if(ss_.bad() || ss_.fail())
-
throw std::runtime_error("lexi_cast4 failed");
-
-
return out;
-
}
-
-
private:
-
std::stringstream ss_;
-
};
-
-
// Overload for output type auto deduction.
-
template <class Input>
-
lexi_cast4_helper lexi_cast4(const Input& in)
-
{
-
return lexi_cast4_helper(in);
-
}
-
// Overload for user-specified output type.
-
// Instead of returning conversion helper,
-
// this version returns output type.
-
template <class Output, class Input>
-
Output lexi_cast4(const Input& in)
-
{
-
return lexi_cast4_helper(in);
-
}
![]() |
|
| Previous Post « Server side connection/session lingering « |
Next Post » So, You Think C++ is Slower than C » |








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