Использование Boost.Range в интерфейсах C++

У меня полиморфный интерфейс

struct Interface {
  Interface(SomeType& other)
  : range([=](){ return other.my_range(); }), /*...*/ {}
  Interface(SomeOtherType& other)
  : range([=](){ return other.some_range(); }), /*...*/ {}

  const std::function<Range(void)> range;
  /// ...
};

Элементы в обоих диапазонах относятся к одному типу (например, int), но типы возвращаются my_range() и по some_range() разные, например, один может быть filtered counting range а другой transformed filtered counting range, Для интерфейса мне нужен один Range тип.

Я пытался использовать boost::any_range но производительность значительно хуже. Я хотел бы избежать копирования элементов диапазона в vector и возвращая вектор вместо.

Есть ли альтернативы any_range и копирование?

2 ответа

Решение

Вроде, но не совсем.

Вы хотите получать доступ к данным последовательно, когда не знаете, как они хранятся. У вас есть три варианта:

  • Скопируйте данные в контейнер с известным форматом (опция "вектор возврата").
  • Используйте полиморфизм во время компиляции, чтобы выбрать правильный метод доступа (как это делают алгоритмы std, это невозможно из-за использования вами интерфейса).
  • Используйте полиморфизм времени выполнения, чтобы выбрать правильный метод доступа.

Поэтому второе невозможно из-за ограничений, которые вы хотите использовать интерфейс. Первый и третий оба идут с накладными расходами.

Очевидный способ сделать третье any_range, Но это не единственный способ, в зависимости от того, что вы хотите сделать. Проблема с any_range в том, что в простом цикле for-each есть три виртуальных вызова для каждого элемента: приращение, сравнение и разыменование.

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

struct Interface {
    Interface(SomeType& other)
    : traverse([=](std::function<void(int)> body) {
      for (int i : other.my_range()) body(i);
    }) {}

    const std::function<void (std::function<void(int)>)> traverse;
};

Конечно, это работает только до тех пор, пока способы использования диапазона очень ограничены.

Если известны только 2 известных типа (или фиксированное число типов), то альтернативой может быть Boost.Variant. Вот пример использования:

#include <boost/variant.hpp>
#include <functional>

struct SomeType
{
    typedef int range_t;
    range_t my_range() const { return 1; }
};

struct SomeOtherType
{
    typedef double range_t;
    range_t some_range() const { return 3.14; }
};

typedef std::function<SomeType::range_t (void)> SomeTypeRange;
typedef std::function<SomeOtherType::range_t (void)> SomeOtherTypeRange;
typedef boost::variant<SomeTypeRange, SomeOtherTypeRange> Range;

struct Interface
{
  Interface(const SomeType& other)
  : range( SomeTypeRange([=](){ return other.my_range(); }) ) {}

  Interface(const SomeOtherType& other)
  : range( SomeOtherTypeRange([=](){ return other.some_range(); }) ) {}

  Range range;
};

struct print_me_visitor : public boost::static_visitor<void>
{
public:
    void operator()( const SomeTypeRange& i_st ) const
    {
        std::cout << "SomeTypeRange: " << i_st() << std::endl;
    }

    void operator()( const SomeOtherTypeRange& i_sot ) const
    {
        std::cout << "SomeOtherTypeRange: " << i_sot() << std::endl;
    }
};

int main()
{
    SomeType st;
    SomeOtherType sot;
    Interface i1( st );
    Interface i2( sot );

    boost::apply_visitor( print_me_visitor(), i1.range );
    boost::apply_visitor( print_me_visitor(), i2.range );

    return 0;
}
Другие вопросы по тегам