Инициализация std::vector с помощью итеративных вызовов функций
На многих языках существуют генераторы, которые помогают инициализировать коллекции. В С++, если вы хотите равномерно инициализировать вектор, можно написать:
std::vector<int> vec(10, 42); // get 10 elements, each equals 42
Что делать, если вы хотите генерировать разные значения на лету? Например, инициализируйте его 10 случайными значениями или последовательными номерами от 0 до 9? Этот синтаксис был бы удобен, но он не работает в С++ 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);
. Но я не нашел ничего подобного в стандартной библиотеке. Я пропустил это? Есть ли другая причина, почему первый конструктор не дошел до стандарта?
Ответы
Ответ 1
К сожалению, стандартного средства для этого нет.
В вашем конкретном примере вы можете использовать Boost.Iterator counting_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, вам нужен указатель на функцию или тип объекта функции с вложенным result_type
. У Ламбдаса, по какой-то причине, этого нет. Вы можете передать лямбду объекту std::function
(или boost::function
), который определяет это. Вот пример с std::function
. Можно только надеяться, что Boost.Iterator будет использовать Boost.ResultOf когда-нибудь, который будет использовать decltype
, если BOOST_RESULT_OF_USE_DECLTYPE
определен.
Ответ 2
Мир слишком велик для того, чтобы С++ поставлял решение для всего. Однако С++ не хочет быть огромным супермаркетом, полным готовых блюд для каждого мыслимого неба. Скорее, это небольшая, хорошо оборудованная кухня, в которой вы, шеф-повар С++, можете приготовить любое решение, которое вы хотите.
Здесь глупый и очень грубый пример генератора последовательности:
#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());
В том же ключе вы можете написать более общий гаджет, который "называет данный функтор заданным числом раз" и т.д. (например, возьмите объект функции по шаблонной копии и используйте счетчики в качестве счетчиков повторений и разыменование вызывает функтор).
Ответ 3
Если вы используете компилятор, который поддерживает lambdas, как вы используете в своем вопросе, то шансы довольно хороши, он также включает 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);
Что касается того, почему это не было включено в стандартную библиотеку, я действительно даже не могу догадаться. Я полагаю, это можно рассматривать как аргумент в пользу диапазонов, поэтому алгоритм будет иметь диапазон, и у нас будет простой способ создать диапазон от пары начала/конца или пары начала/счета. Я не уверен, что полностью согласен с этим, но диапазоны действительно работают хорошо в некоторых случаях, но в других они мало или вообще не имеют смысла. Я не уверен, что без дополнительной работы у нас есть ответ, который действительно намного лучше, чем пара итераторов.
Ответ 4
Никто не упоминал boost:: assign, поэтому я представлю его здесь:
Пример
#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 << ' ';
}
}
Выход
41 18467 6334 26500 19169 15724 11478 29358 26962
0 1 2 3 4 5 6 7 8 9
Ответ 5
Вы можете использовать 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;
}
}