Вопрос по initialization, c++11, c++, stdvector, iterator – Инициализация std :: vector с помощью итеративных вызовов функций

21

Во многих языках существуют генераторы, которые помогают инициализировать коллекции. В C ++, если кто-то хочет инициализировать вектор равномерно, можно написать:

std::vector<int> vec(10, 42); // get 10 elements, each equals 42

Что если кто-то хочет генерировать разные значения на лету? Например, инициализировать его 10 случайными значениями или последовательными числами от 0 до 9? Этот синтаксис был бы удобен, но этоdoes not work в C ++ 11:

int cnt = 0;
std::vector<int> vec(10, [&cnt]()->int { return cnt++;});

Есть ли хороший способ инициализировать коллекцию с помощью итеративных вызовов функций? В настоящее время я использую этот уродливый шаблон (не намного более читаемый / короткий, чем цикл):

std::vector<int> vec;
int cnt = 0;
std::generate_n(std::back_inserter(vec), 10, [&cnt]()->int { return cnt++;});

Есть кое-что, что могло бы помочь, и это объяснило бы отсутствие первого конструктора. Я могу представить себе итератор, который принимает функцию и количество вызовов, так что конструктор

vector ( InputIterator first, InputIterator last);

будет применимо. Но я не нашел ничего подобного в стандартной библиотеке. Я пропустил это? Есть ли еще одна причина, по которой первый конструктор не добрался до стандарта?

С другой стороны, C ++ является идеальным языком дляyou написать повторяемый объект, который реализует желаемую семантику! Kerrek SB
Я не нахожу этот шаблон таким уродливым, но мне нравится этот вопрос. Мне любопытно узнать, есть ли другая причина помимо того, чтобы избегать раздувания интерфейса класса. Gorpik

Ваш Ответ

5   ответов
0

Вы можете использовать SFINAE для формирования таблицы:

#include <iostream>
#include <vector>

template <int n> struct coeff    { static int const value = coeff<n-1>::value + 3; };
template <>      struct coeff<0> { static int const value = 0; };

template<int... values> struct c1 {static int const value[sizeof...(values)];};
template<int... values> int const c1<values...>::value[] = {values...};

template<int n, int... values> struct c2 : c2< n-1, coeff<n-1>::value, values...> {};
template<int... values> struct c2< 0, values... > : c1<values...> {};

template<int n> struct table : c2< n > {
    static std::vector< unsigned int > FormTable()
    {
        return std::vector< unsigned int >( & c2< n >::value[0], & c2< n >::value[n] );
    }
};

int main()
{
    const auto myTable = table< 20 >::FormTable();

    for ( const auto & it : myTable )
    {
        std::cout<< it << std::endl;
    }
}
@ overrider нет, выше это способ установить вектор во время компиляции
Я не достаточно глубок, чтобы понять это полностью. :( Будет ли это работать, когда количество элементов неизвестно во время компиляции? Roman Shapovalov
6

Мир слишком велик для C ++, чтобы найти решение для всего. Однако C ++ не хочет быть огромным супермаркетом, полным готовых блюд на любой вкус. Скорее, это небольшая, хорошо оборудованная кухня, в которойyouШеф-повар C ++ может приготовить для вас любое решение.

Вот глупый и очень грубый пример генератора последовательности:

#include <iterator>

struct sequence_iterator : std::iterator<std::input_iterator_tag, int>
{
    sequence_iterator() : singular(true) { }
    sequence_iterator(int a, int b) : singular(false) start(a), end(b) { }
    bool singular;
    int start;
    int end;

    int operator*() { return start; }
    void operator++() { ++start; }

    bool operator==(sequence_iterator const & rhs) const
    {
        return (start == end) == rhs.singular;
    }
    bool operator!=(sequence_iterator const & rhs) const
    {
        return !operator==(rhs);
    }
};

Теперь вы можете сказать:

std::vector<int> v(sequence_iterator(1,10), sequence_iterator());

В том же духе вы можете написать более общий гаджет, который "вызывает данный функтор заданное число раз" и т. Д. (Например, возьмите объект функции по шаблонной копии и используйте счетчики в качестве счетчиков повторения; разыменование вызывает функтор).

Почему бы просто не наследовать отstd::iterator для typedefs? :)
@ overrider: да, итератор «generate_n» был бы хорош ... как и getline-итератор. Ну, может быть, однажды. Но сейчас вы знаете, как сделать свой собственный!
Не былоsingular на конструктор не по умолчанию явно инициализировать в ложь?
@Xeo: Потому что я не могу понять, как! :-) [Редактировать:] Готово, спасибо!
Я согласен с вами, что генерация последовательности может быть слишком специфичной для включения в C ++. Но мой вопрос более общий: я спрашиваю о любых итеративных вызовах функций, например, Инициализация ГСЧ. Фактически, смысл состоит в том, чтобы объединить векторный конструктор и generate_n, чтобы сделать его более элегантным. Roman Shapovalov
15

К сожалению, нет стандартного средства для этого.

Для вашего конкретного примера вы можете использовать Boost.Iteratorcounting_iterator как это:

std::vector<int> v(boost::counting_iterator<int>(0),
    boost::counting_iterator<int>(10));

Или даже с Boost.Range, как это:

auto v(boost::copy_range<std::vector<int>>(boost::irange(0,10)));

(copy_range будет в основном простоreturn std::vector<int>(begin(range), end(range)) и это отличный способ приспособить конструкцию полного диапазона к существующим контейнерам, которые поддерживают построение диапазона только с двумя итераторами.)

Теперь для случая общего назначения с функцией генератора (например,std::rand), Здесьfunction_input_iterator, При увеличении он вызывает генератор и сохраняет результат, который затем возвращается при разыменовании.

#include <vector>
#include <iostream>
#include <cmath>
#include <boost/iterator/function_input_iterator.hpp>

int main(){
  std::vector<int> v(boost::make_function_input_iterator(std::rand, 0),
      boost::make_function_input_iterator(std::rand,10));
  for(auto e : v)
    std::cout << e << " ";
}

Живой пример.

К сожалению, так какfunction_input_iterator не использует Boost.ResultOf, вам нужен указатель на функцию или тип объекта функцииthat has a nested result_type, У лямбд по какой-то причине этого нет. Выcould передать лямбду кstd::function (или жеboost::function) объект, который определяет это.Вот пример сstd::function, Можно только надеяться, что Boost.Iterator когда-нибудь будет использовать Boost.ResultOf, который будет использоватьdecltype еслиBOOST_RESULT_OF_USE_DECLTYPE определено.

@ Конрад: можно только надеяться. : / В других новостях обновлен код, чтобы показывать лучшие / более краткие способы для конкретного случая последовательности.
Твой ответ - самый близкий к моим ожиданиям. Спасибо! Roman Shapovalov
@Konrad: я объясняю, почему я использую локальную структуру в коде C ++ 11 чуть ниже кода. По существу, лямбда-тип не определяетresult_type для любой причины. :( А в C ++ 03 вы всегда можете пойти не очень хорошим путем, определяя его как нелокальную структуру.
Duh. Я молчаливо предполагал, что лямбды определили это, но, если быть честным, нет никаких оснований для & # x2013; у нас естьresult_of, в конце концов. Будет ли Boost.Iterator добавить поддержку для этого?
Хм. Локальные структуры не могут использоваться в качестве аргументов шаблона в C ++ 03, а в C ++ 11 у вас есть лямбды.
2

Никто не упомянулповышение :: назначенияИтак, я представлю это здесь:

Example

#include <iostream>
#include <vector>
#include <boost/assign/std/vector.hpp> 
#include <cstdlib>

int main()
{
    std::vector<int> v1;
    std::vector<int> v2;
    boost::assign::push_back(v1).repeat_fun(9, &rand);
    int cnt = 0;
    boost::assign::push_back(v2).repeat_fun(10, [&cnt]()->int { return cnt++;});
    for (auto i : v1)
    {
        std::cout << i << ' ';
    }
    std::cout << std::endl;
    for (auto i : v2)
    {
        std::cout << i << ' ';
    }
}

Output

41 18467 6334 26500 19169 15724 11478 29358 26962
0 1 2 3 4 5 6 7 8 9

Приятная особенность, но не инициализация. Немного короче, чем std :: generate_n, но требует повышения. Roman Shapovalov
2

Если вы используете компилятор, который поддерживает лямбды, как вы используете в своем вопросе, то, скорее всего, он также включает в себяstd::iotaчто, по крайней мере, делает подсчет немного чище:

std::vector <int> vec(10);
std::iota(begin(vec), end(vec), 0);

Для этого сценария (и многих других, я думаю) мы бы действительно предпочлиiota_n хоть:

namespace stdx {
template <class FwdIt, class T>
void iota_n(FwdIt b, size_t count, T val = T()) {
    for ( ; count; --count, ++b, ++val)
        *b = val;
}
}

Который в вашем случае вы бы использовали следующим образом:

std::vector<int> vec;

stdx::iota_n(std::back_inserter(vec), 10);

Относительно того, почему это не было включено в стандартную библиотеку, я действительно даже не могу предположить. Я полагаю, что это можно рассматривать как аргумент в пользу диапазонов, поэтому алгоритм будет принимать диапазон, и у нас будет простой способ создать диапазон из пары начало / конец или пара начало / счет. Я не уверен, что полностью с этим согласен - диапазоны, кажется, работают хорошо в некоторых случаях, но в других они не имеют большого смысла или вообще не имеют смысла. Я не уверен, что без дополнительной работы у нас есть ответ, который действительно намного лучше, чем пара итераторов.

Похожие вопросы