Составление адаптеров в Boost::range

Я начал играть с Boost::Range, чтобы иметь конвейер ленивых преобразований в C++. Теперь моя проблема заключается в том, как разделить конвейер на более мелкие части. Предположим, у меня есть:

int main(){
  auto map = boost::adaptors::transformed; // shorten the name
  auto sink = generate(1) | map([](int x){ return 2*x; })
                          | map([](int x){ return x+1; })
                          | map([](int x){ return 3*x; });
  for(auto i : sink)
    std::cout << i << "\n";
}

И я хочу заменить первые две карты на magic_transform, то есть:

int main(){
  auto map = boost::adaptors::transformed; // shorten the name
  auto sink = generate(1) | magic_transform()
                          | map([](int x){ return 3*x; });
  for(auto i : sink)
    std::cout << i << "\n";
}

Как написать magic_transform? Я посмотрел Boost ::Документация Range, но я не могу в ней разобраться.

Дополнение: я хочу написать такой класс:

class magic_transform {
    ... run_pipeline(... input) {
        return input | map([](int x){ return 2*x; })
                     | map([](int x){ return x+1; });
};

person bruno nery    schedule 05.11.2012    source источник
comment
Каков тип возврата generate? Меня беспокоит, что ваш sink содержит ссылку на временное имя, срок действия которого заканчивается точкой с запятой.   -  person Mankarse    schedule 06.11.2012
comment
Тип возвращаемого значения generate – boost::iterator_range. Подробнее см. liveworkspace.org/code/841508d3b54bed4181d4e9fb6058200f.   -  person bruno nery    schedule 06.11.2012
comment
Магическое преобразование не получает входной параметр. Вы написали это как нулевую функцию.   -  person Yakk - Adam Nevraumont    schedule 06.11.2012
comment
Хорошо, это будет больше класс, чем функция. См. выше.   -  person bruno nery    schedule 06.11.2012
comment
Ваши потребности все еще неясны. Это синтаксис канала, который вам нужен? Преобразование вокруг связанных лямда-выражений дает функциональность и внешнее использование |. Вы действительно хотите | внутри вашего magic_transform, и если да, то почему?   -  person Yakk - Adam Nevraumont    schedule 06.11.2012
comment
Да, я хочу | в моем magic_transform — я разрабатываю программу, которая состоит в основном из конвейера, и конвейер может быть сложным. Я хочу иметь возможность разбить его на более мелкие трубы :)   -  person bruno nery    schedule 06.11.2012


Ответы (2)


Самая сложная проблема — выяснить тип возвращаемого значения в коде. decltype и лямбды плохо сочетаются (см. здесь), поэтому нам нужно подумать об альтернативе способ:

auto map = boost::adaptors::transformed;

namespace magic_transform
{
   std::function<int(int)> f1 = [](int x){ return 2*x; };
   std::function<int(int)> f2 = [](int x){ return x+1; };
   template <typename Range>
   auto run_pipeline(Range input) -> decltype(input | map(f1) | map(f1))
   {
        return input | map(f1) | map(f2);
   }
}

...
auto sink = magic_transform::run_pipeline(generate(1))
                          | map([](int x){ return 3*x; });

Простое решение — вставить лямбды в std::function, чтобы мы могли использовать decltype для вывода типа возвращаемого значения. Я использовал пространство имен magic_transform в примере, но вы можете адаптировать этот код в класс, если хотите. Вот ссылка, адаптирующая ваш код к приведенному выше.

Кроме того, использование std::function здесь может быть излишним. Вместо этого вы можете просто объявить две обычные функции (пример).

Я также экспериментировал с boost::any_range, похоже, есть некоторая несовместимость с лямбда-выражениями C+11 и т. д. Самое близкое, что я смог получить, было следующим (пример):

auto map = boost::adaptors::transformed;
using range = boost::any_range<
               const int,
               boost::forward_traversal_tag,
               const int&,
               std::ptrdiff_t
               >;

namespace magic_transform
{
    template <typename Range>
    range run_pipeline(Range r)
    {
        return r | map(std::function<int(int)>([](int x){ return 2*x; }))
             | map(std::function<int(int)>([](int x){ return x+1; }));
    }
}

int main(){
  auto sink = magic_transform::run_pipeline(boost::irange(0, 10))
                          | map([](int x){ return 3*x; });
  for(auto i : sink)
    std::cout << i << "\n";
}
person Jesse Good    schedule 06.11.2012
comment
Прежде чем задать вопрос здесь, я попробовал decltype и нашел его слишком многословным для этого приложения (в итоге вы дважды пишете конвейер). any_range, OTOH, позволяет указать только ввод и вывод, верно? - person bruno nery; 06.11.2012
comment
Теперь я вижу, any_range указывает вывод (значение и ссылка). Интересно. - person bruno nery; 06.11.2012
comment
Кроме того, вы также можете использовать any_range для параметра. Например: range run_pipeline(range r) определяет конвейер, который работает с диапазоном целых чисел и возвращает диапазон целых чисел. - person bruno nery; 06.11.2012
comment
@brunonery: Да, я не смог заставить any_range работать с вашим примером generate(1). - person Jesse Good; 07.11.2012
comment
@brunonery: я обнаружил, что вам нужно изменить тег итератора на std::forward_iterator_tag, вот рабочий пример. - person Jesse Good; 07.11.2012

думаю будет работать:

auto magic_transform()->decltype(boost::adaptors::transformed(std::function<int(int)>())
{
    std::function<int(int)> retval = [](int x){ return [](int x){ return x+1; }(2*x);
    return boost::adaptors::transformed(retval);
}

но это, вероятно, не то, что вы ищете. :) (Шутки в приведенном выше коде: связанные лямбды для 2 * x + 1, использование decltype в основном в реализации для поиска возвращаемого типа),

Глядя на исходный код для http://www.boost.org/doc/libs/1_46_1/boost/range/adaptor/transformed.hpp, тип, который magic_transform хочет вернуть, — это boost::range_detail::transform_holder<T>, где T — тип функции.

Когда вы делаете это в стеке с лямбда-выражениями, T оказывается очень узким типом. Если вы хотите обойти абстрактные преобразования, не раскрывая всех деталей, использование std::function<outtype(intype)> может быть разумным (будут небольшие накладные расходы во время выполнения).

Надеюсь, это сработает.

person Yakk - Adam Nevraumont    schedule 05.11.2012
comment
Да, цепочка лямбд - это не то, что я искал :), пожалуйста, проверьте мое обновление. На самом деле я мог бы использовать auto и decltype, но мне хотелось чего-то более общего. - person bruno nery; 06.11.2012
comment
Итак, вы хотите каким-то образом связать (хорошо скомбинировать по порядку) преобразования вместе и получить в результате преобразование? - person Yakk - Adam Nevraumont; 06.11.2012
comment
Не только трансформеры, но и в конечном итоге фильтры и другие адаптеры диапазонов. - person bruno nery; 06.11.2012
comment
Тип результата адаптера диапазона задокументирован. например, transformed возвращает boost::transformed_range‹Range› boost.org/doc/libs/1_52_0/libs/range/doc/html/range/reference/ - person Akira Takahashi; 06.11.2012