2015-07-17 5 views
4

Рассмотрим простое обобщение станд :: преобразования я написал для N входных итераторов:Обобщение станд :: преобразование

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

template <typename InputIterator, typename OutputIterator, typename NaryOperator, typename... InputIterators> 
OutputIterator transform (InputIterator first, InputIterator last, OutputIterator result, 
NaryOperator op, InputIterators... iterators) { 
    while (first != last) { 
     *result = op(*first, *iterators++...); 
     ++result; ++first; 
    } 
    return result; 
} 

int main() { 
    const std::vector<int> a = {1,2,3,4,5}; 
    const std::vector<double> b = {1.2, 4.5, 0.6, 2.8, 3.1}; 
    const std::vector<std::string> c = {"hi", "howdy", "hello", "bye", "farewell"}; 
    std::vector<double> result(5); 
    transform (a.begin(), a.end(), result.begin(), 
     [](int i, double d, const std::string& s)->double {return i + d + s.length();}, 
     b.begin(), c.begin()); 
    for (double x : result) std::cout << x << ' '; // 4.2 11.5 8.6 9.8 16.1 
} 

То, что я хочу сделать сейчас, это позволит векторы a, b, c иметь различные длины (и аргумент InputIterator last можно удалить), и в этом случае transform будет продолжать преобразование до тех пор, пока не будет использован самый длинный вектор, используя значения по умолчанию для более коротких векторов.

Я думал, что это будет просто изменение размеров всех коротких контейнеров в функции transform, но аргументы transform не дают информации о том, сколько времени все контейнеры. Есть ли способ вычислить в пределах transform, как долго каждый из контейнеров, тем самым получая максимальную длину и тем самым заполняя значения по умолчанию для более коротких контейнеров? В идеале, используя только синтаксис:

transform (OutputIterator result, NaryOperator op, InputIterators... iterators); 

Обновление: После идеи Раманы, я имею в виду использовать что-то вроде:

template <typename OutputIterator, typename NaryOperator, typename... InputIterators> 
OutputIterator transform (OutputIterator result, NaryOperator op, InputIterators... first, InputIterators... last) { 
    while (true) { 
     *result = op((first == last ? 
      typename std::iterator_traits<InputIterators>::value_type() : *first++)...); 
     ++result; 
    } 
    return result; 
} 

Но

transform (result.begin(), 
    [](int i, double d, const std::string& s)->double {return i + d + s.length();}, 
    a.begin(), b.begin(), c.begin(), a.end(), b.end(), c.end()); 

не компилируется. Я думаю, потому что компилятор не знает, где начинается last....

Так что я попытался это следующее:

template <typename OutputIterator, typename NaryOperator, typename... InputIteratorsPairs> 
OutputIterator transform (OutputIterator result, NaryOperator op, InputIteratorsPairs... pairs) { 
    while (true) { 
     *result = op((pairs.first == pairs.second ? 
      typename InputIteratorsPairs::first_type() : *pairs.first++)...); 
     ++result; 
    } 
    return result; 
} 

Но

transform_ (result.begin(), 
    [](int i, double d, const std::string& s)->double {return i + d + s.length();}, 
    std::make_pair(a.begin(), a.end()), std::make_pair(b.begin(), b.end()), std::make_pair(c.begin(), c.end())); 

не компилируется либо (и мне не нравится синтаксис в любом случае).

+0

['std :: distance()'] (http://en.cppreference.com/w/cpp/iterator/distance)? –

+0

Как насчет переопределения итераторов ++, чтобы остановить продвижение, когда оно находится в конце, и вернуть значение по умолчанию для контейнера? –

+0

@ Парамагнитный круассан. Вы имеете в виду вызов 'iterators ++ ..', пока какой-либо из них не достигнет конца, а затем вычислите длину, используя std :: distance? Как узнать, когда достигается «конец»? – prestokeys

ответ

4
#include <cstddef> 
#include <utility> 
#include <tuple> 
#include <iterator> 

bool all(bool a) 
{ 
    return a; 
} 

template <typename... B> 
bool all(bool a, B... b) 
{ 
    return a && all(b...); 
} 

template <typename OutputIterator, typename NaryOperator, typename... InputIterators, std::size_t... Is> 
OutputIterator transform(OutputIterator result, NaryOperator op, std::index_sequence<Is...>, InputIterators... iterators) 
{ 
    auto tuple = std::make_tuple(iterators...); 
    while (!all(std::get<2*Is>(tuple) == std::get<2*Is + 1>(tuple)...)) 
    { 
     *result = op((std::get<2*Is>(tuple) != std::get<2*Is + 1>(tuple) 
       ? *std::get<2*Is>(tuple)++ 
       : typename std::iterator_traits<typename std::tuple_element<2*Is, decltype(tuple)>::type>::value_type{})...); 
     ++result; 
    } 
    return result; 
} 

template <typename OutputIterator, typename NaryOperator, typename... InputIterators> 
OutputIterator transform(OutputIterator result, NaryOperator op, InputIterators... iterators) 
{ 
    return transform(result, op, std::make_index_sequence<sizeof...(InputIterators)/2>{}, iterators...); 
} 

Тесты:

int main() 
{ 
    const std::vector<int> a = {1,2,3,4,5}; 
    const std::vector<double> b = {1.2, 4.5, 0.6, 2.8, 3.1}; 
    const std::vector<std::string> c = {"hi", "howdy", "hello", "bye", "farewell"}; 
    std::vector<double> result(5); 

    transform(result.begin(), 
     [] (int i, double d, const std::string& s) -> double 
     { 
      return i + d + s.length(); 
     }, 
     a.begin(), a.end(), 
     b.begin(), b.end(), 
     c.begin(), c.end()); 

    for (double x : result) std::cout << x << ' '; 
} 

Выход:

4.2 11.5 8.6 9.8 16.1 

DEMO

+0

с C++ 1z спрятанными выражениями выражение 'while' может быть заменено на' while (((std :: get <2*Is> (tuple)! = Std :: get <2*Is + 1> (tuple)) || ...)) ' –

+0

Если вы собираетесь зайти так далеко, почему бы не сделать их на основе диапазона? – Yakk

+0

@Yakk: а именно? –

2
pairs.first == pairs.second ? 
     typename InputIteratorsPairs::first_type() : *pairs.first++ 

Вы инициализируете инициализацию итератора с левой стороны от : вместо того типа, на который указывает итератор. Также у вас есть бесконечный цикл и неопределенное поведение, потому что вы продолжаете увеличивать result. Вот версия, которая фиксирует эти проблемы (требует <algorithm> и не обязательно наиболее эффективным:.

bool any(std::initializer_list<bool> vs) 
{ 
    return std::any_of(begin(vs), end(vs), [](bool b) { return b; }); 
} 

template<typename OutputIterator, typename NaryOperator, typename... InputIteratorsPairs> 
OutputIterator transform(OutputIterator result, NaryOperator op, InputIteratorsPairs... pairs) { 
    while (any({(pairs.first != pairs.second)...})) { 
     *result = op((pairs.first == pairs.second ? 
      typename InputIteratorsPairs::first_type::value_type() : *pairs.first++)...); 
     ++result; 
    } 
    return result; 
} 
1

Это решение на основе диапазона Вместо действующих на итераторы, мы работаем на диапазонах итераторы

Диапазон. это пара итераторов с некоторыми помощниками.Это минимальная реализация с только некоторые помощники написано:

template<class It> using it_value_type = 
    typename std::iterator_traits<It>::value_type; 
template<class It> using it_reference = 
    typename std::iterator_traits<It>::reference; 

template<class It> 
struct range_t { 
    It b, e; 
    range_t():b(),e(){} 
    range_t(It s, It f):b(s),e(f){} 
    template<class C, class=std::enable_if_t<!std::is_same<std::decay_t<C>,range_t>{}>> 
    range_t(C&& c): 
    range_t(std::begin(c),std::end(c)) 
    {} 
    It begin() const { return b; } 
    It end() const { return e; } 

    bool empty() const { return begin()==end(); } 
    it_reference<It> front() const { return *begin(); } 
    it_reference<It> back() const { return *std::prev(end()); } 

    range_t pop_front() const { 
    if (empty()) return {}; 
    else return {std::next(begin()), end()}; 
    } 
}; 

Функция, чтобы создать range_t проще:

template<class C, class It=std::decay_t<decltype(std::begin(std::declval<C&>()))>> 
range_t<It> range(C&& c) { 
    return {std::begin(c), std::end(c)}; 
} 

Помощник сделать проверку кучу bool с, чтобы увидеть, если они все true проще:

bool all_of(std::initializer_list<bool> il) { 
    return std::all_of(il.begin(), il.end(), [](bool b){return b;}); 
} 

Теперь для работы. Спектр на основе реализация преобразования первая:

template<class Sink, class Operator, class...Its> 
Sink transform(Sink sink, Operator op, range_t<Its>... srcs) { 
    while(!all_of({srcs.empty()...})) { 
    *sink++ = op((srcs.empty()?it_value_type<Its>{}:srcs.front())...); 
    using discard=int[]; 
    (void)discard{0, 
     ((srcs = srcs.pop_front()),0)... 
    }; 
    } 
    return sink; 
} 

И нестандартный диапазон реализация, который только вперед к вышесказанному:

template<class Sink, class Operator, class...Srcs> 
Sink transform(Sink sink, Operator op, Srcs&&... srcs) { 
    return transform(sink, op, range(srcs)...); 
} 

они должны быть в состоянии существовать как перегрузки друг друг ,

Включить это, чтобы остановить, когда заканчивается первый конец диапазона - обмен all_of для any_of.

live example.

Смежные вопросы