Я внес предложение по совершенствованию стандарта С++ относительно алгоритма
std::iota в группе [url=https://groups.google.com/a/isocpp.org/forum/?fromgroups#!forum/std-proposals]ISO C++ Standard - Future[/url]
В текущем стандарте С++ алгоритм
std::iota объявлен в заголовочном файле
<numeric> следующим образом:
template <class ForwardIterator, class T>
void iota(ForwardIterator first, ForwardIterator last, T value);
Как видно из объявления, тип возращаемого значения у данного алгоритма
void, то есть функция ничего на самом деле не возвращает.
Вообще-то, это крайне не рачительно не возвращать из функции значение, когда предоставляется такая возможность передать пользователю как можно больше информации. Тип
void у функций надо указывать лишь тогда, когда действительно ничего полезного через возвращаемое значение вы передать не можете. Здесь же, то есть в случае с алгоритмом
std::iota, совершенно другая ситуация.
Но сначала для тех, кто не знаком с этим алгоритмом, который появился в стандарте С++ 2011, будет полезно узнать, что делает этот алгоритм. Этот алгоритм последовательно присваивает элементам произвольной последовательности из диапазона [first, last} значения value, которые при каждом присвоении увеличиваются на единицу.
То есть, если, например, нужно какому-то одномерному массиву присвоить последовательные значения 1, 2, 3,,, и т.д., то проще всего это сделать с помощью этого алгоритма.
const size_t N = 10;
int a[N];
std::iota( std::begin( a ), std::end( a ), 1 );
И тогда элемент массива
a[0] будет иметь значение
1,
a[2] -
2, и т.д. вплоть до элемента
a[9], который будет иметь значение
10.
Это очень удобная штука, когда у вас размер массива достаточно большой, что вручную его инициализировать в виде
int a[N] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
не представляется возможным.
Спрашивается, ну, и причем здесь возвращаемый тип алгоритма
void? Чем он мешает?
На этот недостаток алгоритма сразу же наталкиваешься, когда нужно решить менее тривиальную задачу. И такая задача, которая моментально выявила недостаток этого алгоритма, попалась мне на глаза на одном сайте. Формулировка задачи простая. Есть двумерный массив размерность 10 * 10. Элементы этого двумерного массива нужно заполнить последовательно числами, начиная с 1 и до 10 * 10, то есть до 100.
Если использовать алгоритм
std::iota в том виде, в каком он сейчас существует, то придется для каждой следующей строки двумерного массива самостоятельно рассчитывать начальное значение. То есть для первой строки массива начально значение задавалось бы в виде
value = 1. Затем, когда наступит черед заполнять следующую строку значениями, нужно будет выполнить предложение
value += 10; и т.д.
Нельзя ли как-то обойтись без этих промежуточных вычислений и поручить самому алгоритму отслеживать текущее значение?
Это можно сделать, если переопределить алгоритм
std::iota таким образом, чтобы он возвращал заключительное значение параметра
value. То есть нужно алгоритм
std::iota определить следующим образом:
template <typename ForwardIterator, typename T>
inline T iota( ForwardIterator first, ForwardIterator last, T value )
{
for ( ; first != last; ++first, ++value ) *first = value;
return ( value );
}
В этом определении тип возвращаемого значения заменен с
void на тип параметра
value, то есть
T.
Как теперь можно воспользоваться этим алгоритмом, чтобы инициализировать двумерный массив? Вот обобщенная функция, которая эта делает
template <typename T, size_t N, size_t M>
void init_array( T ( &a )[N][M], T init_value )
{
std::accumulate( std::begin( a ), std::end( a ), init_value,
[]( T s, T ( &x )[M] )
{
return ( iota( std::begin( x ), std::end( x ), s ) );
} );
}
Здесь внутри алгоритма
std::accumulate для каждой строки двумерного массива вызывается модифицированный алгоритм
iota, который в свою очередь возвращает заключительное значение параметра
value, которое служит начальным значением при последующей итерации по строкам массива.
Это очень удобно! Например, для матрицы размером 10 * 10 при следующих вызовах
const size_t N = 10;
int a[N][N];
usr::init_array( a, 1 );
usr::init_array( a, - 100 );
можно последовательно два раза инициализировать матрицу в виде
цитата: |
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
|
и
цитата: |
-100 -99 -98 -97 -96 -95 -94 -93 -92 -91 -90 -89 -88 -87 -86 -85 -84 -83 -82 -81 -80 -79 -78 -77 -76 -75 -74 -73 -72 -71 -70 -69 -68 -67 -66 -65 -64 -63 -62 -61 -60 -59 -58 -57 -56 -55 -54 -53 -52 -51 -50 -49 -48 -47 -46 -45 -44 -43 -42 -41 -40 -39 -38 -37 -36 -35 -34 -33 -32 -31 -30 -29 -28 -27 -26 -25 -24 -23 -22 -21 -20 -19 -18 -17 -16 -15 -14 -13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 |
|
В любом случае замена типа
void возвращаемого значения на что-то значимое у этого алгоритма оказывается более полезным и расширяет возможности применения этого алгоритма.
Есть еще один не менее интересный вариант - это замена возвращаемого типа
void на конечное значение итератора, то есть
last. Тогда этот алгоритм можно было бы использовать в связке с другими алгоритмами, когда конечный итератор после работы этого алгоритма был бы начальным итератором для другого алгоритма.
Например, мы хотим половину элементов одномерного массива заполнить последовательными значениями по возрастанию, начиная с 1, а все остальные эелементы массива заполнить числом N.
Это можно было бы сделать объяединив два алгоритма в одной строке:
std::fill( std::iota( a, a + N / 2, 1 ), a + N, N );
Здесь возвращаемый конечный итератор из диапазона, задланного для алгоритма
std::iota, становится начальным итератором диапазона алгоритма
std::fill Лично я пока склоняюсь к первому варианту изменения
std::iota, который я и предложил комитету по стандартизации.