Такие функции-члены контейнеров как: size, empty, front, back и data - хорошо знакомы всем C++ программистам. Они давно являются незыблемым инструментом при разработке. Но, оказывается, в комитет поступило предложение, которое призвано улучшить работу с этими функциями.

Эта статья основана, и, фактически является переводом вот этого предложения N4017, которое поступило в комитет в мае прошлого года (2014). Автор Riccardo Marcangelo - кто это, узнать не удалось. Что же он предлагает?

Рикардо, говорит: давайте добавим в библиотеку глобальные функции: std::size, std::empty, std::front, std::back, и std::data, которые сейчас существуют в виде функций-членов контейнеров. Включение этих функций обеспечит преимущества в отношении безопасности, эффективности и универсальности. Все подробности ниже (статья очень короткая).

Обычной задачей, особенно при работе с устаревшим кодом, является определение количества элементов в "сишном статическом" массиве, иначе говоря, длины массива. Обычно, это достигается за счет использования оператора sizeof, который применяется ко всему массиву, и делением результата на размер одного элемента массива:

Для удобства, подобный функционал часто "оборачивают" в макрос и называют как "ARRAYSIZE" или "countof":

Проблема заключается в том, что такой макрос не безопасен. Если вместо статического массива будет передан указатель, код успешно скомпилируется, но работать правильно не будет. При этом мы даже не получим предупреждений от компилятора, не говоря уже об ошибках. У Microsoft есть более "хитрая" версия макроса _countof, которая "умеет" выдавать ошибки на этапе компиляции, если ей будет передан указатель:

Используя возможности C++11, мы можем заменить небезопасный макрос функцией, со спецификатором constexpr. ​Constexpr позволяет гарантировать, что функция возвращает константу времени компиляции:​

Что-то похожее сделано и у Microsoft, но они "обошлись" без constexpr:

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

Однако, как заметил Stephan T. Lavavej, std::distance - это нагруженный (verbose) и неэффективный способ для многих контейнеров. Дело в том, что многие контейнеры определяют функцию-член size, которая имеет константную сложность O(1), то есть выполняется за постоянное время вне зависимости от количества элементов. Напротив, std::distance всегда будет иметь линейную сложность для контейнеров, итераторы которых слабее, чем итераторы произвольного доступа.

К тому же алгоритм std::distance не использует спецификатор constexpr.

Интересно, что Александер Степанов (создатель STL), в своих "Заметках о программировании", пишет следующее: "Я сделал size функцией-членом в попытке угодить комитету по стандартизации. Я знал, что begin, end и size должны быть глобальными функциями, но не был готов к еще одному спору с членами комитета". К счастью, в C++11 включены глобальные функции std::begin() и std::end(), однако, жаль, что нет глобального std::size.

Было бы здорово добавить глобальную функцию std::size(), которую можно было бы использовать, как с контейнерами, так и со статическими массивами. Это позволило бы писать более обобщенны код. Также, было бы полезно иметь и другие глобальные функции, такие как: std::empty(), std::front(), std::back(), и std::data().

Тогда мы смогли бы писать примерно так:

Используя C++11, можно получить размер массива альтернативным способом:

Недостатком такого подхода является то, что мы теряем возможность обобщить его одновременно и для массивов, и контейнеров STL.

Далее в статье затрагивается вопрос реализации, который я опускаю. Кстати, предложение уже прошло две доработки, и, в рамках одной из них, из рассмотрения были убраны функции front, back.

Мы не скоро увидим то, о чем здесь рассказывается, но все-таки это предложение мне показалось интересным и простым одновременно. Поэтому решил поделиться. Спасибо Рикардо! И читателю за внимание!