Какая приемлемая C++ идиома для генерации чисел от 0 до n-1 в произвольном типе в массиве или векторе?
Другими словами, как бы я писал:
template <typename T> vector<T> generate_integers_upto(size_t n);
или
template <typename T> T* generate_integers_upto(size_t n);
Это скорее зависит от того, что вы хотите делать с этими цифрами.
Если вам действительно нужен диапазон, а не контейнер, то boost::irange
будет более чем достаточным. Ему даже не нужна какая-либо [существенная] память!
Это позволяет вам делать такие классные вещи:
#include <iostream>
#include <boost/range/irange.hpp>
using boost::irange;
using std::cout;
int main()
{
for (auto i : irange(0, 42))
cout << i << ' ';
}
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
Идиоматическим способом было бы возвращение по значению. Вы можете использовать std::iota
чтобы заполнить вектор для простоты, но это вторично:
#include <vector>
#include <numeric>
template<typename T>
std::vector<T> generate(std::size_t n)
{
std::vector<T> v(n);
std::iota(std::begin(v), std::end(v), T());
return v;
}
Просто верните значение и пусть компилятор решит, что (RVO, move return и т.д.) Является более эффективным:
template<typename T>
std::vector<T> generate( std::size_t n , T begin = 0u )
{
std::vector<T> result( n );
std::iota( std::begin( result ) , std::end( result ) , begin );
return result;
}
Обратите внимание, что тип возвращаемого по умолчанию - unsigned int
. Конечно, вы можете изменить значение, переданное функции для изменения возвращаемого значения, или указать явно тип возвращаемого значения:
int main()
{
auto sequence = generate<float>( 100 );
}
Эта реализация основана на стандартном библиотечном алгоритме std::iota()
.
std::iota()
.
Если вы хотите, чтобы функция создала для вас массив, верните std::vector
по значению.
Возвращение ссылки, как показывает ваш первый пример, либо недействительно (если вектор был локальной переменной, которая теперь была уничтожена), либо странно и подвержена ошибкам (так как теперь есть вектор где-то, которому нужно как-то управлять).
Возврат указателя, предположительно к выделенному массиву, подвержен ошибкам, поскольку нет ничего, чтобы убедиться, что вызывающий абонент освобождает его правильно.
Более гибкой альтернативой является выбор диапазона итераторов. Это может иметь смысл перегрузить его для пары итераторов:
std::vector<int> v(10); // non-empty vector
generate(v.begin(), v.end()); // replace existing elements
итератор и размер:
std::vector<int> v; // empty vector
generate(back_inserter(v), 10); // insert new elements
Обратите внимание, что библиотека С++ 11 имеет std::iota
которая действует как первая версия (и может быть использована для реализации любого из них), но не похожа на вторую.
std::vector<T> x(n); std::iota(x.begin(), x.end(), T());