Составляющие адаптеры в Boost:: range

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

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, i.e.:

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 documentation, но я не могу понять его.

Добавление. Я хочу написать класс следующим образом:

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

Ответ 1

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

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; });

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

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

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

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";
}

Ответ 2

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

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);
}

но это, вероятно, не то, что вы ищете.:) (Шутки в приведенном выше коде: закодированные lambdas для 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 - тип функции.

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

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