У меня есть функция, которая принимает многомерный std::vector
и требует, чтобы глубина (или количество измерений) передавалась как параметр шаблона. Вместо жесткого кодирования этого значения я хотел бы написать constexpr
функцию, которая будет принимать std::vector
и возвращать глубину как unsigned integer
значение.
Например:
std::vector<std::vector<std::vector<int>>> v =
{
{ { 0, 1}, { 2, 3 } },
{ { 4, 5}, { 6, 7 } },
};
// Returns 3
size_t depth = GetDepth(v);
Это необходимо сделать во время компиляции, потому что эта глубина будет передана функции шаблона в качестве параметра шаблона:
// Same as calling foo<3>(v);
foo<GetDepth(v)>(v);
Есть какой-либо способ сделать это?
std::vector
- это время выполнения, а не время компиляции. Если вы хотите контейнер размера во время компиляции, посмотритеstd::array
. Также; помните, что этоconstexpr
означает только то, что « может быть оценено во время компиляции» - нет никаких обещаний, что так и будет . Это может быть оценено во время выполнения.std::vector
s вложено друг в друга. Например, сstd::vector<std::vector<int>> v;
,GetDepth(v);
вернет 2, так как это 2-мерный вектор. Размер не имеет значения.vector
не всегда лучший способ сделать что-то. Ручная двумерная или трехмерная индексация одного плоского вектора может быть более эффективной в зависимости от варианта использования. (Просто целочисленная математика вместо погони за указателями с внешних уровней.)rank
для этого запроса типы массивов (в соответствии с математической номенклатурой для тензоров). Возможно, это лучшее слово здесь, чем "глубина".Ответы:
Классическая шаблонная проблема. Вот простое решение, например, как работает стандартная библиотека C ++. Основная идея состоит в том, чтобы иметь рекурсивный шаблон, который будет подсчитывать одно за другим каждое измерение, с базовым регистром 0 для любого типа, который не является вектором.
Тогда вы можете использовать это так:
Редактировать:
Хорошо, я закончил общую реализацию для любого типа контейнера. Обратите внимание, что я определил тип контейнера как что-либо, что имеет правильно сформированный тип итератора согласно выражению,
begin(t)
гдеstd::begin
импортируется для поиска ADL иt
является lvalue типаT
.Вот мой код вместе с комментариями, чтобы объяснить, почему все работает, и тестовые примеры, которые я использовал. Обратите внимание, это требует C ++ 17 для компиляции.
источник
std::vector<T>
специализацию и изменить ее на другой тип контейнера. Единственное, что вам нужно, это базовый вариант 0 для любого типа, для которого вы не специализируетеПредполагая, что контейнером является любой тип, который имеет
value_type
иiterator
типы элементов (стандартные библиотечные контейнеры удовлетворяют этому требованию) или массив в стиле C, мы можем легко обобщить решение Cruz Jean :Типы контейнеров могут быть дополнительно ограничены при необходимости.
источник
Вы можете определить следующий шаблон класса,
vector_depth<>
который соответствует любому типу:Этот основной шаблон соответствует базовому случаю, который завершает рекурсию. Затем определите его соответствующую специализацию для
std::vector<T>
:Эта специализация соответствует
std::vector<T>
и соответствует рекурсивному случаю.Наконец, определите шаблон функции
GetDepth()
, который обращается к шаблону класса выше:Пример:
Выход этой программы:
источник
std::vector
, но, например,GetDepth(v)
гдеv
неint
будет компилироваться. Было бы лучше иметьGetDepth(const volatile T&)
и просто вернутьсяvector_depth<T>::value
.volatile
просто позволяет охватить больше вещей, будучи максимально квалифицированным cv