2005年12月15日

STL のバインダとリファレンスへのリファレンス問題

STL に含まれる bind1st と bind2nd は for_each や find などの関数と組み合わせて使うと便利です。しかしながら、リファレンス (参照) を引数に取る関数の引数をバインドすることはできないので注意が必要です。

 

注意が必要ですと言いつつ、自分がこの問題を知らずにはまりました。次のようなコードで問題は起きます。

#include <algorithm>
#include <functional>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

void print(const string& s1,  const string& s2) {
    cout << s1 << ": " << s2 << endl;
}

int main() {
    vector<string> l;
    l.push_back("foo");
    l.push_back("bar");
    l.push_back("baz");
    for_each(l.begin(), l.end(), bind1st(ptr_fun(print), "info"));
    return 0;
}

print 関数は 2つの文字列を const 参照として受け取って、それらを : で連結して表示するという単純なものです。 for_each の行では、print 関数の第1引数を "info" にバインドして、

info: foo
info: bar
info: baz

という出力をしようとしています。ptr_fun は関数をバインド可能な関数オブジェクト (ファンクタ) に変換するためのものです。このコードをコンパイルすると次のようなエラーになります。

% g++ test.cpp
/usr/include/c++/3.3/bits/stl_function.h: In instantiation of `std::binder1st<std::pointer_to_binary_function<const std::string&, const std::string&, void> >':
test.cpp:17:   instantiated from here
/usr/include/c++/3.3/bits/stl_function.h:359: error: forming reference to
   reference type `const std::string&'
/usr/include/c++/3.3/bits/stl_function.h:361: error: forming reference to
   reference type `const std::string&'
/usr/include/c++/3.3/bits/stl_function.h:367: error: forming reference to
   reference type `const std::string&'
/usr/include/c++/3.3/bits/stl_function.h: In function `
   std::binder1st<_Operation> std::bind1st(const _Operation&, const _Tp&) [with
   _Operation = std::pointer_to_binary_function<const std::string&, const
   std::string&, void>, _Tp = char[5]]':
test.cpp:17:   instantiated from here
/usr/include/c++/3.3/bits/stl_function.h:379: error: no matching function for
   call to `std::binder1st<std::pointer_to_binary_function<const std::string&,
   const std::string&, void> >::binder1st(const
   std::pointer_to_binary_function<const std::string&, const std::string&,
   void>&, const std::basic_string<char, std::char_traits<char>,
   std::allocator<char> >&)'
/usr/include/c++/3.3/bits/stl_function.h:352: error: candidates are:
   std::binder1st<std::pointer_to_binary_function<const std::string&, const
   std::string&, void> >::binder1st(const
   std::binder1st<std::pointer_to_binary_function<const std::string&, const
   std::string&, void> >&)
/usr/include/c++/3.3/bits/stl_algo.h: In function `_Function
   std::for_each(_InputIter, _InputIter, _Function) [with _InputIter =
   __gnu_cxx::__normal_iterator<std::string*, std::vector<std::string,
   std::allocator<std::string> > >, _Function =
   std::binder1st<std::pointer_to_binary_function<const std::string&, const
   std::string&, void> >]':
test.cpp:17:   instantiated from here
/usr/include/c++/3.3/bits/stl_algo.h:157: error: no match for call to `(
   std::binder1st<std::pointer_to_binary_function<const std::string&, const
   std::string&, void> >) (std::basic_string<char, std::char_traits<char>,
   std::allocator<char> >&)'

大量のエラーメッセージが出ていますが、ポイントは error: forming reference to reference type `const std::string&' の部分です。bind1st の実装の中でリファレンスへのリファレンスを作ろうとしているのがエラーの原因です。

この部分のエラーメッセージで検索すると Radium Software DevelopmentReference to Reference という記事が見つかりました。Stroupstrup によるレポートもあり、割とよく知られた問題のようです。

手っ取り早い解決策は Boost を使うというもので、確かに STL の bind1st の代わりに boost::bind1st を使うと問題なくコンパイルが通って動きます。Boost なら ptr_fun もいらなくなるので、

    // #include <boost/functional.hpp> が必要
    for_each(l.begin(), l.end(), boost::bind1st(print, "info"));

のようにすっきり書けます。

はてなブックマーク経由でboost::bind を使うと楽かもとのご指摘を受けました。ありがとうございます。

    // #include <boost/bind.hpp> が必要
    for_each(l.begin(), l.end(), boost::bind(print, "info", _1));

まとめ

このように C++ は奥が深く、興味が尽きません。Effective STLでは最後の第50項で Boost を紹介しているところで、このリファレンスのリファレンス問題に軽く触れています。ptr_fun や mem_fun などについては きまぐれ日記: ファンクタが簡潔にまとまっていて参考になります。

おまけ

検索エンジン経由で来る人のために、 ICC (Intel C++ Compiler) でのエラーメッセージも載せておきます。

/usr/include/c++/3.3/bits/stl_function.h(358): error: reference to reference is not allowed
              const typename _Operation::first_argument_type& __y)
                                                            ^
          detected during instantiation of class "std::binder1st<_Operation> [with _Operation=std::pointer_to_binary_function<const std::string &, const std::string &, void>]" at line 17 of "test.cpp"

/usr/include/c++/3.3/bits/stl_function.h(361): error: reference to reference is not allowed
    operator()(const typename _Operation::second_argument_type& __x) const {
                                                              ^
          detected during instantiation of class "std::binder1st<_Operation> [with _Operation=std::pointer_to_binary_function<const std::string &, const std::string &, void>]" at line 17 of "test.cpp"

/usr/include/c++/3.3/bits/stl_function.h(367): error: reference to reference is not allowed
    operator()(typename _Operation::second_argument_type& __x) const {
                                                        ^
          detected during instantiation of class "std::binder1st<_Operation> [with _Operation=std::pointer_to_binary_function<const std::string &, const std::string &, void>]" at line 17 of "test.cpp"

/usr/include/c++/3.3/bits/stl_function.h(367): error: function "std::binder1st<_Operation>::operator()(const _Operation::second_argument_type &) const [with _Operation=std::pointer_to_binary_function<const std::string &, const std::string &, void>]" has already been declared
    operator()(typename _Operation::second_argument_type& __x) const {
    ^
          detected during instantiation of class "std::binder1st<_Operation> [with _Operation=std::pointer_to_binary_function<const std::string &, const std::string &, void>]" at line 17 of "test.cpp"

compilation aborted for test.cpp (code 2)