Ответ 1
Начиная с С++ 11 вы можете использовать std::tuple_size
для std::array
, чтобы получить размер как постоянную времени компиляции. Видеть
Размер std::array
известен во время компиляции, но функция члена size
не является статичной. Есть ли причина для этого? Это немного неудобно, чтобы не было возможности вычислить размер без создания экземпляра объекта. (Ну, я знаю специализацию std::tuple_size
, но он не работает для классов, полученных из std::array
.)
Начиная с С++ 11 вы можете использовать std::tuple_size
для std::array
, чтобы получить размер как постоянную времени компиляции. Видеть
Для этого нет веской причины. На самом деле boost::array<T, N>
, предшественник std::array<T,N>
, фактически определяет static size_t size(){return N;}
(хотя современная более полезная версия также должна использовать constexpr
).
Я согласен с OP, что это неудачное упущение и недоиспользование языковых особенностей.
Проблема
Я столкнулся с этой проблемой раньше, и логика приводит к паре решений. Ситуация OP следующая: у вас есть класс, который происходит от std::array
, и вам нужно получить доступ к размеру во время компиляции.
#include<array>
template<class T...>
struct myarray : std::array< something that depends on T... >{
... very cool functions...
};
а позже вы
template<class Array, size_t N = ???>
functionOnArrayConcept(Array const& a){...}
Где вам нужно знать N
во время компиляции.
Как и сейчас, нет кода ???
, который вы можете написать, который работает как для std::array
, так и myarray
, потому что std::tuple_size<myarray<...>>
не будет работать.
Решение
(это было предложено @T.C здесь Доступ к максимальной глубине шаблона при компиляции?. Я просто копирую его здесь.)
template<class T, std::size_t N>
auto array_size_impl(const std::array<T, N>&)
-> std::integral_constant<std::size_t, N>;
template<class Array>
using array_size = decltype(array_size_impl(std::declval<const Array&>()));
template<class Array>
constexpr auto static_size() -> decltype(array_size<Array>::value){
return array_size<Array>::value;
}
template<class Array>
constexpr auto static_size(Array const&) -> decltype(static_size<Array>()){
return static_size<Array>();
}
Теперь вы можете использовать его как это:
template<class Array, size_t N = static_size<Array>()>
functionOnArrayConcept(Array const& a){...}
Если вы используете std::tuple_size
уже, к сожалению (я думаю), вам нужно специализировать std::tuple_size
для каждого из ваших производных классов:
namespace std{
template<class... T> // can be more complicated if myarray is not parametrized by classes only
struct tuple_size<myclass<T...>> : integral_constant<size_t, static_size<myclas<T...>>()>{};
}
(По-моему это вызвано другой ошибкой в конструкции STL, что std::tuple_size<A>
не имеет значения по умолчанию template<class A> struct tuple_size : A::size(){}
.)
Решения, выходящие за этот момент, почти устарели по сравнению с @T.C. решение, описанное выше. Я буду держать их здесь только для справки.
Решение 1 (идиоматическое)
Если функция отключена от вашего класса, вы должны использовать std::tuple_size
, потому что это единственный стандартный способ доступа к размеру std::array
во время компиляции. Поэтому вам нужно сделать это: 1) предоставить специализацию std::tuple_size
, и если вы можете управлять myclass
, 2) std::array
не имеет static size()
, но ваш производный класс может (что упрощает решение).
Таким образом, это может быть довольно общее решение в рамках STD, которое состоит в специализации std::tuple_size
.
(К сожалению, предоставление специализации в std::
иногда является единственным способом создания реального общего кода. См. http://en.cppreference.com/w/cpp/language/extending_std)
template<class... T>
struct myarray : std::array<...something that depends on T...>{
... very cool functions...
static constexpr size_t size(){return std::tuple_size<std::array<...something that depends on T...>>::value;}
};
namespace std{
// specialization of std::tuple_size for something else that `std::array<...>`.
template<class... T> // can be more complicated if myarray is not parametrized by classes only
struct tuple_size<myclass<T...>> : integral_constant<size_t, myclass<T...>::size()>{};
}
// now `functionOnArrayConcept` works also for `myarray`.
(static size_t size()
можно вызывать по-разному, и могут быть другие способы вывести размер базы myarray
без добавления какой-либо статической функции в size
.)
Примечание
В компиляторах я попробовал следующий трюк, который не работает. Если бы это сработало, все обсуждение было бы менее важным, потому что std::tuple_size
не было бы так необходимо.
template<class ArrayConcept, size_t N = ArrayConcept{}.size()> // error "illegal expression", `std::declval<ArrayConcept>()` doesn't work either.
functionOnArrayConcept(ArrayConcept const& a){...}
Концептуализация
Из-за этого недостатка в реализации (или спецификации?) std::array
, благодаря которой единственный способ извлечь время компиляции size
- через std::tuple_size
. Тогда std::tuple_size
концептуально является частью необходимого интерфейса std::array
. Поэтому, когда вы наследуете от std::array
, вы также "наследуете" std::tuple_size
в некотором смысле. И, к сожалению, вам нужно сделать это для дальнейших выводов. Это концепция этого ответа.
Решение 2 (взлом GNU)
Если вы используете библиотеку GNU STD (которая включает в себя gcc
и clang
), существует хак, который можно использовать без добавления какого-либо кода, и это с помощью элемента _M_elems
, который имеет (член ) тип ::_AT_Type::_Type
(aka type T[N]
) std::array<T, N>
.
Эта функция будет эффективно вести себя как статическая функция ::size()
(за исключением того, что она не может использоваться для экземпляров объекта) std::array
или любого типа, полученного из std::array
.
std::extent<typename ArrayType::_AT_Type::_Type>::value
который может быть заключен в:
template<class ArrayType>
constexpr size_t array_size(){
return std::extent<typename ArrayType::_AT_Type::_Type>::value
}
Эта работа, потому что тип элемента _AT_Type::_Type
наследуется. (Интересно, почему GNU оставила эту деталь реализации public
. Еще одно упущение?)
Решение 3 (портативный хак)
Наконец, решение, использующее рекурсию шаблона, можно выяснить, каков размер базы std::array
.
template<class Array, size_t N=0, bool B = std::is_base_of<std::array<typename Array::value_type, N>, Array>::value>
struct size_of : size_of<Array, N + 1, std::is_base_of<std::array<typename Array::value_type, N+1>, Array>::value>{};
template<class Array, size_t N>
struct size_of<Array, N, true> : std::integral_constant<size_t, N>{};
// this is a replacement for `static Array::size()`
template<class Array, size_t N = size_of<Array>::value>
constexpr size_t static_size(){return N;}
// this version can be called with an object like `static Array::size()` could
template<class Array, size_t N = size_of<Array>::value>
constexpr size_t static_size(Array const&){return N;}
Вот как получится:
struct derived : std::array<double, 3>{};
static_assert( static_size<std::array<double, 3>>() == 3 );
static_assert( static_size<derived>() == 3 );
constexpr derived d;
static_assert( static_size(d) == 3 );
Если эта функция вызывается с некоторым типом, не связанным с std::array
, это даст ошибку рекурсии. Если вам нужна "мягкая" ошибка, вам нужно добавить специализацию.
template<class Array>
struct size_of<Array, 250, false> {};
где 250
обозначает большое число, но меньше предела рекурсии. (Я не знаю, как получить этот номер автоматически, я знаю только, что предел рекурсии в моем компиляторе равен 256
.)
Это действительно может быть статичным, однако это сломает интерфейс "контейнер", который не будет хорошо работать с другими универсальными алгоритмами, которые ожидают, что контейнеры будут иметь функцию-член size()
. Однако не стоит беспокоиться о том, что std::array::size()
- это функция constexpr
, поэтому нет никаких связанных с ней служебных данных.
UPDATE:
г. Jrok указал, что можно вызвать статические функции-члены с "нормальным" синтаксисом. Ниже приведен пример, когда он не будет:
#include <array>
struct array {
static unsigned int size()
{
return 0;
}
};
template <typename T>
static auto do_stuff(T& data) -> decltype(data.size())
{
typedef decltype(data.size()) size_type;
size_type (T::*size_calc)() const = &T::size;
size_type n = 0;
for (size_type i = 0, e = (data.*size_calc)(); i < e; ++i)
++n;
return n;
}
int main()
{
// Below is fine:
std::array<int, 5> data { 1, 2, 3, 4, 5 };
do_stuff(data);
// This, however, won't work as "size()" is not a member function.
#if 0
array fake;
do_stuff(fake);
#endif
}
array::size
constexpr
, поэтому, если хранимый тип не имеет конструктора или деструктора, операция array_t().size()
вряд ли будет иметь какой-либо эффект времени исполнения. Вы можете вставлять его в аргумент шаблона, чтобы убедиться в его отсутствии. В противном случае он выглядит как код времени выполнения.
Я думаю, что он нестатический просто для единообразия с другими контейнерами. Например, вы можете создать для него функцию-указатель-член. Однако открытие истинного обоснования чего-либо часто требует серьезных исследований. Возможно, авторы никогда не думали об этом.
Другая вещь, которая приходит на ум, состоит в том, что некоторые специальные функции, такие как operator () ()
, не могут быть статическими, поэтому любое оппортунистическое применение статики может быть только по частям. Общие проблемы лучше решаются единообразно, даже если это означает изменение основного языка.
Обратите внимание, что Microsoft Visual С++ в настоящее время не поддерживает constexpr (http://msdn.microsoft.com/en-us/library/hh567368.aspx), поэтому следующий действующий код не будет работать:
array<int,3> dog;
array<double, dog.size( )> cat;
Следующий класс предоставляет статическую переменную времени компиляции:
/**
* hack around MSVC 2012 lack of size for const expr
*/
template <typename T, int N>
struct vcarray : public std::array<T,N> {
static const size_t ArraySize= N;
};
который можно использовать как:
vcarray<double,3> cat;
vcarray<double,cat.ArraySize> dog;
По-моему, не имеет смысла делать функцию члена size
static
, поскольку она не добавляет значения. Это можно сделать static
, но вы ничего не получаете от него.
Как разработан класс array
, вы можете запросить размер заданного объекта array
, не зная/не помня его точный тип (включая его размер) в том месте, где вам нужен размер. Это удобство, и оно устраняет возможность совершать ошибки копирования/редактирования. Вы можете написать такой код:
std::array<int, 5> blah;
// 50 lines of code
do_something_with(blah.size()); // blah knows its size
Как вы можете видеть, в том месте, где я потребляю размер массива, я на самом деле не помню, что это было, но мой код будет работать в любом случае независимо от того, что это за значение, и независимо от того, может быть, день Меняю тип массива на другой размер.
Поскольку функция size
просто возвращает параметр шаблона, компилятор может тривиально доказать, что возвращаемое значение является константой времени компиляции и соответственно оптимизируется (функция также constexpr
, поэтому вы также можете использовать возвращаемое значение в качестве шаблона параметр или перечисление).
Теперь, что будет отличаться, если мы создадим функцию-член size
static
?
Если size
была функцией static
, вы все равно могли бы использовать статическую функцию-член точно таким же образом (то есть на экземпляре объекта в "не статическом режиме" ), но это будет "мошенничество". В конце концов, это то, что уже работает в любом случае, является ли элемент static
или нет.
Кроме того, теперь у вас есть возможность вызвать функцию-член без экземпляра объекта. Хотя на первый взгляд это кажется хорошим, на самом деле это не является преимуществом для шаблона класса array
(... где возвращаемый размер является параметром шаблона).
Чтобы вызвать функцию-член без объекта (т.е. в методе "static
member function" ), вы должны правильно квалифицировать функцию с именем класса и его соответствующими параметрами шаблона.
Другими словами, вы должны написать что-то вроде:
std::array<int, 5> blah;
// 50 lines of code
do_something_with(std::array<int,5>::size()); // I must tell size what to return
Теперь, что мы получили от вызова функции size
? Ничего. Чтобы вызвать функцию, нам нужно было указать правильные параметры шаблона, который включает в себя размер.
Это означает, что больше и не меньше, чем мы должны предоставить информацию, которую мы хотим запросить. Вызов функции не говорит нам ничего, что мы еще не знали.