(How do you) lower (or upper) your strings, the STL way
Posted on March 12th, 2007 at 21:41 by fr3@K

一直以來, 對 C++ Standard Library 裡面沒有字串大小寫轉換的 function, 除了納悶還是納悶. 在 <algorithm> 裡面沒有, 即便是提供了典型 fat interfacestd::basic_string<> , 也獨漏 upper/lower.


舉兩個例子; 既然有了標準容器都支援的 std::basic_string<>::size 又何必要有 std::basic_string<>::length? 與搜尋相關的 member function 如 std::basic_string<>::find/find_first_of 等, 全部都可以在 <algorithm> 找到可更被泛用的 function template.

好吧, Standard Library 裡面隨手可用的 algorithm/facility 這麼多, 這一定是為了簡化 interface 而刻意省略的 (讓我們先忘記它其實是個 fat interface 的事實), 要不了幾行 code 一定就能寫出一個 STLified 的 function (template). 有人可能會這樣說. 那我們就來試試看.

首先要定義一個 STLified 的 interface, 要能使用 iterator 語法, 以及提供 char/wchat_t transparency1. 這裡提出的是個類似 std::copy 的 interface:

template <class InputIterator, class OutputIterator>
void copy_lower(
    InputIterator first,
    InputIterator last,
    OutputIterator dest);

這個 interface 可以被這樣使用:

template <class InputIterator>
void foo(
    const std::string& str,
    const std::wstring& wstr,
    InputIterator first, InputIterator last)
{
    std::string bar(str);
    copy_lower(bar.begin(), bar.end(), bar.begin());

    std::wstring faux;
    faux.reserve(wstr.size()); // optionally, preallocates memory
    copy_lower(wstr.begin(), wstr.end(), std::back_inserter(faux));

    typedef typename
        std::iterator_traits<InputIterator>::value_type
            char_type;
    typedef
        std::basic_string<char_type, char_traits<char_type> >
            xstring;
    xstring foobar;
    copy_lower(first, last, std::back_inserter(foobar));
}

接著我們來試著實做. 嘗試 1, copy_lower_c, 使用 C 的 global locale:

#include <string>
#include <algorithm>
#include <cctype>

template <class InputIterator, class OutputIterator>
void copy_lower_c(
    InputIterator first,
    InputIterator last,
    OutputIterator dest);
{
    std::transform(first, last, dest, tolower);
}

copy_lower_c 的實做非常簡單, 也幾乎符合之前提出的需求, 只可惜 tolower 是給 char 用的 (相對於 wchar_t)2.

C++ 對 wchar_t 的支持顯然比較好, 用 C++ 的 locale 應該能搞定. 研究 reference 後的確看到不只一個能拿來當零件用的 algorithm/facility. 嘗試 2, copy_lower_cpp, 使用 C++ 的 global locale:

#include <string>
#include <iterator>
#include <algorithm>
#include <locale>

template <class CharType>
class lowerer
{
public:
    explicit lowerer (const std::locale& loc)
            : loc_(loc)
        {}
    CharType operator() (CharType ch)
        {
            return std::tolower(ch, loc_);
        }
    std::locale loc_;
};

template <class InputIterator, class OutputIterator>
void copy_lower_cpp(
    InputIterator first, InputIterator last,
    OutputIterator dest)
{
    typedef typename
        std::iterator_traits<InputIterator>::value_type
            char_type;
    std::transform(
        first, last, dest,
        lowerer<char_type>(std::locale()));
}

本來期望可以用 std::bind2nd<> 把一個 std::locale 的 instance 綁在 std::tolower 的第二個參數, 但 std::bind2nd<> 只吃符合 Adaptable Binary Functionfunctional object, 也就是說一樣要我們自己寫一個給 std::tolower<> 這個 function template 的 adapter class. Code 的複雜度與美觀程度與 copy_lower_cpp 版本不相上下.

嘗試 3, copy_lower_boost, 用 Boost.Lambda 來避免人工製作這個 functional object adapter class:

#include <string>
#include <iterator>
#include <algorithm>
#include <locale>

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

template <class InputIterator, class OutputIterator>
void copy_lower_boost(
    InputIterator first,
    InputIterator last,
    OutputIterator dest)
{
    typedef typename
        std::iterator_traits<InputIterator>::value_type
            char_type;
    std::transform(
        first, last, dest,
        boost::lambda::bind(
            std::tolower<char_type>,
            boost::lambda::_1,
            std::locale()));
}

應用了 Boost, copy_lower_boost 不但符合之前定下的需求, 其 function body 也只是簡潔一行 code (不算 typedef). 即使如此這樣的一行 code (function body) 依然比不上呼叫一個簡潔的標準函式來得容易以及 error free. 我還是期待如這般功能更強大的字串處理, 在可以避免造成 fat interface 的情況下, 被定義進未來版本的 Standard Library.

以上 source code 可以在 這裡 下載 (LGPL licensed). 解壓縮後在命令列下 `./configure CXXFLAGS="-Iinclude -Wall" && make' 就可以編譯.

  1. 不論使用者給的是 char 或是 wchar_t 的字串, 使用方法是一致的, 並能產出正確的結果. []
  2. 大小寫似乎與 char/wchar_t 無關 (至少從英語系看來是如此). 有可能 copy_lower_c 其實就已經符合需求了. []
del.icio.us:(How do you) lower (or upper) your strings, the STL way digg:(How do you) lower (or upper) your strings, the STL way spurl:(How do you) lower (or upper) your strings, the STL way newsvine:(How do you) lower (or upper) your strings, the STL way furl:(How do you) lower (or upper) your strings, the STL way Y!:(How do you) lower (or upper) your strings, the STL way 黑米共享書籤:(How do you) lower (or upper) your strings, the STL way 推推王:(How do you) lower (or upper) your strings, the STL way
Previous Post
« Firefox Update Breaks Evolution «
Next Post
» Web Application Development Screencasts »

2 Comments »

Comment #2272

tolower 應該是適合在拉丁語系的國家
wchar_t 定義這種 tolower function 反而變得很怪

老實說 我覺得只是要讓東西能動
STL 不是很棒的選擇

不過就美學的觀點來說是很棒的 XD

Comment by spequer — March 13, 2007 @ 2:13


Comment #2275

假設 upper/lower 只對拉丁語系有意義. spequer 別忘了 wchar_t 也包含了拉丁語系, 因此支援 wchar_t 字串的大小寫轉換並不是件那麼怪的事咧.

STL 當然不會是完美的, 譬如說這篇提到的 std::basic_string. 以及 std::basic_istream_buf 理論上只讀取, 無須更動到外部 memory buffer 的內容, 可是卻強迫使用者餵給它 non-const pointer. 等等…

STL 提供了大量應對 day to day programming 的工具. 可不是為了讓我們程式寫得更 `漂亮’ 而誕生的 (但我們都很喜歡這個副作用, 不是嗎?). 或許更重要的是, 那些工具是標準的, 經過測試的, 幾乎可以肯定比我們自己寫要來得更 error free.

Comment by fr3@K — March 13, 2007 @ 11:48


Comments RSS TrackBack URI

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