Шаблон посетителя с использованием boost :: bind и перегруженных функций

Я пытаюсь добавить в свой код шаблон посетителя и хочу, чтобы он оставался как можно более общим. Точнее, я бы не хотел жестко закодировать функцию обратного вызова в моей функции accept. Итак, в качестве параметра функции accept я даю объект boost::function, который затем вызывается посещенным объектом.

Однако моя проблема в том, что я не могу выполнить привязку к перегруженным функциям (потому что boost :: bind не знает, к какой именно функции привязать), и я не могу привести перегруженную функцию к правильной, потому что я не t знать точный тип посещаемого класса (это важно).

Есть ли способ создать то, что я хочу? Я искал SO, но нашел только вопросы о том, как исправить проблему привязки (что происходит путем литья, чего я не могу сделать).

Ниже приведен код, который не компилируется, но показывает, что я хотел бы заархивировать:

#include <string>
#include <vector>
#include <boost/bind.hpp>
#include <boost/function.hpp>

struct A
{
    virtual void acceptVisitor (boost::function<void (A const &)> callbackFnc)
    {
        callbackFnc(*this);
    }
};

struct B : virtual public A {};

std::string printMe (A const & a) { return "A"; }
std::string printMe(B const & a) { return "B"; }


int main()
{
    std::vector<std::string> stringVector;

    boost::function<void (A const &)> bindedFnc = boost::bind(&std::vector<std::string>::push_back, 
        &stringVector, boost::bind(&printMe, _1));

    A A1;
    B A2;

    A1.acceptVisitor(bindedFnc);
    A2.acceptVisitor(bindedFnc);
}

[Edit] Исправлен пример кода, поскольку предыдущая версия (как заметил ildjarn) на самом деле не вызывала функцию accept.


person AVH    schedule 14.05.2011    source источник
comment
Обратите внимание, что вы также не можете взять адрес push_back, потому что он может быть перегружен (а в C ++ 0x он гарантированно будет перегружен), и вы не можете устранить неоднозначность с помощью приведения, потому что тип функции-члена стандартной библиотеки не указано (реализация может добавлять дополнительные перегрузки и / или необязательные параметры к своим функциям-членам).   -  person James McNellis    schedule 14.05.2011
comment
Ниже приведен код, который не компилируется, но показывает то, что я хотел бы заархивировать. Нет, не компилируется; где acceptVisitor играет роль? Где собственно посещение? Попробуйте описать то, что вы хотите, потому что вы не продемонстрировали это эффективно.   -  person ildjarn    schedule 15.05.2011
comment
@ildjarn Хороший момент, исправленный пример кода.   -  person AVH    schedule 15.05.2011


Ответы (2)


Это должно помочь вам на полпути. Он компилируется с Visual C ++ 2010 и g ++ 4.5.1 с использованием Boost 1.46.0. Он не компилируется с реализацией Visual C ++ 2010 C ++ 0x <functional>; Я пока не знаю почему.

Настройка:

#include <iostream>
#include <iterator>
#include <string>
#include <vector>

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

// This helper allows you to do a push_back in a bind; you can't bind
// directly to std::vector::push_back because the type of a Standard
// Library member function is unspecified.
struct do_push_back
{
    typedef void result_type;

    template <typename TSequence, typename TElement>
    void operator()(TSequence* sequence, const TElement& element) const
    {
        sequence->push_back(element);
    }
};

Демонстрация:

// Class hierarchy for demonstration:
struct B { };
struct D : B { };

// Instead of using overlodaed nonmember functions, you can overload
// operator() in a function object.  This allows you to bind to an 
// instance of this function object, not directly to one of the overloads.
struct make_string
{
    typedef std::string result_type;
    std::string operator()(const B&) const { return "B"; }
    std::string operator()(const D&) const { return "D"; }
};

int main()
{
    std::vector<std::string> strings;

    // Note that we do not use a boost::function here:
    auto f = boost::bind(do_push_back(), 
                         &strings, 
                         boost::bind(make_string(), _1));

    // Call our 'f' with B and D objects:
    f(B());
    f(D());

    std::copy(strings.begin(), strings.end(),
              std::ostream_iterator<std::string>(std::cout));
}

Результат:

BD

Вот почему это только половина решения: вы не можете сохранить результат вызова boost::bind в boost::function. Проблема в том, что когда вы используете boost::function<void(const B&)> для хранения объекта связанной функции, он всегда будет передавать const A& в качестве аргумента связанной функции.

Даже если вы вызываете объект boost::function с аргументом D, он преобразуется в const B&. При использовании boost::function теряется много информации о типе; эта потеря информации о типе необходима для того, чтобы boost::function можно было использовать в качестве универсального контейнера вызываемых объектов.

Однако это не означает, что вы не можете передавать связанный объект функции; вам просто нужно использовать шаблоны, чтобы информация о типе не терялась:

template <typename TFunction>
void test(std::vector<std::string>& strings, TFunction f)
{
    f(B());
    f(D());
}

// In main():
test(strings, f);

// Or, if you don't have C++0x's "auto", you can pass the bound 
// function object directly:
test(strings, boost::bind(do_push_back(), 
                          &strings, 
                          boost::bind(make_string(), _1)));

К сожалению, чтобы не потерять информацию о типе, необходимо передать связанный объект функции в шаблон функции. Это означает, что ваша идея сделать acceptVisitor виртуальную функцию-член не будет работать с этим решением (невозможно иметь шаблон виртуальной функции).

В любом случае, надеюсь, это будет вам полезно.

person James McNellis    schedule 14.05.2011
comment
@ james-mcnellis Спасибо за четкое объяснение. Жалко, что то, что я хочу, кажется невозможным в C ++. Интересно, как вообще такое реализовано? Можно ли просто исправить функцию обратного вызова? Или есть какой-то другой трюк, о котором я не знаю? - person AVH; 15.05.2011

Возможно, вам будет полезен общий шаблон Visitor из библиотеки Loki.

См. http://loki-lib.sourceforge.net.

#include <iostream>
#include <Loki/Visitor.h>

struct Animal : public Loki::BaseVisitable<void, Loki::DefaultCatchAll, false>
{
};

struct Cat : public Animal
{
    LOKI_DEFINE_VISITABLE();
};

struct Dog : public Animal
{
    LOKI_DEFINE_VISITABLE();
};

struct Flower : public Loki::BaseVisitable<void, Loki::DefaultCatchAll, false>
{
};

struct Tulip : public Flower
{
    LOKI_DEFINE_VISITABLE();
};

struct AnimalAndFlowerVisitor 
    : public Loki::BaseVisitor
    , public Loki::Visitor<Cat, void, false>
    , public Loki::Visitor<Dog, void, false>
    , public Loki::Visitor<Tulip, void, false>
{
    void Visit(Dog & dog)
    {
        std::cout << "Do something with the dog\n";
    }

    void Visit(Cat & cat)
    {
        std::cout << "Do something with the cat\n";
    }

    void Visit(Tulip & tulip)
    {
        std::cout << "Do something with the tulip\n";
    }
};

int main(int argc, char* argv[])
{
    Dog dog;
    Cat cat;
    Tulip tulip;

    Animal & animalDog = dog;
    Flower & tulipFlower = tulip;

    AnimalAndFlowerVisitor visitor;
    animalDog.Accept(visitor);    // will print "Do something with the dog"
    tulipFlower.Accept(visitor);  // will print "Do something with the tulip"

    return 0;
}
person Aleksey Malov    schedule 17.08.2012