Использование boost::bind для std::string::find не компилируется

У меня есть следующий код:

int MimeDocument::GetAttachmentId( std::string const& content_id )
{
    using namespace boost::lambda;
    using boost::lambda::_1;
    using boost::bind;

    int id = 0;

    std::vector<std::string>::iterator it =
        std::find_if( attachment_list_.begin(), attachment_list_.end(),
            bind( &std::string::find, content_id, _1 ) != std::string::npos
        );

    if( it != attachment_list_.end() ) {
        id = std::distance( attachment_list_.begin(), it );
    }

    return id;
}

Который при компиляции на MSVC9 SP1 приводит к тоннам C2780 ошибки компилятора. Вот только некоторые из верхней части списка:

1>c:\code\work\cmake-mds\server\gmmserver\domino\server\interface\dimime.cpp(210) : error C2780: 'boost::_bi::bind_t<_bi::dm_result<MT::* ,A1>::type,boost::_mfi::dm<M,T>,_bi::list_av_1<A1>::type> boost::bind(M T::* ,A1)' : expects 2 arguments - 3 provided
1>        c:\code\work\cmake-mds\build-vc9\third_party\boost\1.48.0\include\boost\bind\bind.hpp(1728) : see declaration of 'boost::bind'
1>c:\code\work\cmake-mds\server\gmmserver\domino\server\interface\dimime.cpp(210) : error C2780: 'boost::_bi::bind_t<Rt2,boost::_mfi::cmf8<R,T,B1,B2,B3,B4,B5,B6,B7,B8>,_bi::list_av_9<A1,A2,A3,A4,A5,A6,A7,A8,A9>::type> boost::bind(boost::type<T>,R (__thiscall T::* )(B1,B2,B3,B4,B5,B6,B7,B8) const,A1,A2,A3,A4,A5,A6,A7,A8,A9)' : expects 11 arguments - 3 provided
1>        c:\code\work\cmake-mds\build-vc9\third_party\boost\1.48.0\include\boost\bind\bind_mf2_cc.hpp(223) : see declaration of 'boost::bind'
1>c:\code\work\cmake-mds\server\gmmserver\domino\server\interface\dimime.cpp(210) : error C2780: 'boost::_bi::bind_t<Rt2,boost::_mfi::mf8<R,T,B1,B2,B3,B4,B5,B6,B7,B8>,_bi::list_av_9<A1,A2,A3,A4,A5,A6,A7,A8,A9>::type> boost::bind(boost::type<T>,R (__thiscall T::* )(B1,B2,B3,B4,B5,B6,B7,B8),A1,A2,A3,A4,A5,A6,A7,A8,A9)' : expects 11 arguments - 3 provided
1>        c:\code\work\cmake-mds\build-vc9\third_party\boost\1.48.0\include\boost\bind\bind_mf2_cc.hpp(212) : see declaration of 'boost::bind'

Любые ошибки компилятора, связанные с boost, практически нечитаемы и бесполезны для меня, поэтому я надеюсь, что кто-нибудь поможет мне разобраться в том, что происходит. Заранее спасибо.

2 ответа

Решение

Есть четыре перегрузки std::string::find:

size_t find(const string& str, size_t pos = 0) const;
size_t find(const char* s, size_t pos, size_t n) const;
size_t find(const char* s, size_t pos = 0) const;
size_t find(char c, size_t pos = 0) const;

Поэтому нужно помочь компилятору выбрать один (устранить неоднозначность), указав, к какой конкретной перегруженной функции относится адрес, например:

boost::bind( static_cast<size_t(std::string::*)(const std::string&, size_t) const>(&std::string::find), content_id, _1, 0)

Скорее уродливо, не так ли?

Обратите внимание, что std::string::find() возвращается std::string::npos (который чаще всего size_t(-1)) при неудачном поиске. Тогда это будет конвертировать size_t(-1) в bool(true) и привести к std::find_if() возвращая свой первый аргумент независимо от того, каковы остальные аргументы.

Результат std::string::find() нужно сравнить с std::string::npos, С помощью boost::bind это будет выглядеть так:

// ...

std::vector<std::string>::iterator it = std::find_if(
      attachment_list_.begin()
    , attachment_list_.end()
    , boost::bind(
          std::not_equal_to<std::string::size_type>()
        , std::string::npos
        , boost::bind(
              static_cast<size_t(std::string::*)(const std::string&, size_t) const>(&std::string::find)
            , &content_id // pass by pointer, don't copy
            , _1
            , 0)
            )
    );

Который не выглядит слишком читабельным либо.

Это может быть чуть-чуть более читабельным с boost::lambda::bind:

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

// ...

std::vector<std::string>::iterator it =
    std::find_if(
          attachment_list_.begin()
        , attachment_list_.end()
        , boost::lambda::constant(std::string::npos) != boost::lambda::bind(
              static_cast<size_t(std::string::*)(const std::string&, size_t) const>(&std::string::find)
            , &content_id // pass by pointer, don't copy
            , boost::lambda::_1
            , 0
            )
    );

Он выглядит наиболее читаемым и элегантным с лямбда C++11:

std::vector<std::string>::iterator it = std::find_if(
      attachment_list_.begin()
    , attachment_list_.end()
    , [&content_id](std::string const& i) { return std::string::npos != content_id.find(i); }
    );

Далее я заметил, что id для неудачного поиска возвращается 0. Это то же самое значение, которое возвращается, когда поиск завершается по первому элементу. Другими словами, вызывающая сторона этой функции не сможет различить неудачный поиск и соответствие первого (0-го) элемента.

Здесь наиболее просто и удобно использовать простой цикл для поиска:

std::string* MimeDocument::GetAttachmentId(std::string const& content_id) {
    for(  std::vector<std::string>::iterator i(attachment_list_.begin()), j(attachment_list_.end())
        ; i != j
        ; ++i
        ) {
        if(std::string::npos != content_id.find(*i))
            return &*i;
    }
    return NULL;
}

Используя эту версию, вызывающая сторона может легко определить между успешным и неудачным поиском и при необходимости узнать соответствующий индекс:

MimeDocument doc;
// ... populate doc
if(std::string* found = doc.GetAttachmentId("1")) {
    // the search was successful.
    size_t found_index = found - &doc.attachment_list_.front();
}

Итак, выбери свой яд...

Типы параметров для bind никак не связаны друг с другом (типы ортогональных шаблонов), и только внутри тела компилятор может определить, какая перегрузка find нужно. Фактически, компилятору разрешается только просматривать объявление функции, чтобы выяснить, что передать, и существуют неоднозначные возможные перегрузки find и компилятор не может использовать связанный тип аргумента, чтобы помочь выяснить, какой из них использовать.

В этом случае я думаю, что может быть проще написать 5-строчный функтор для поиска по вложенной строке.

Другие вопросы по тегам