J'ai une fonction qui prend une _ std::vector
et requiert que la profondeur (ou le nombre de dimensions) soit transmise en tant que paramètre de modèle. Au lieu de coder en dur cette valeur, je voudrais écrire une fonction constexpr
qui prendra le std::vector
et renvoie la profondeur sous la forme d'un unsigned integer
valeur.
Par exemple:
std::vector<std::vector<std::vector<int>>> v =
{
{ { 0, 1}, { 2, 3 } },
{ { 4, 5}, { 6, 7 } },
};
// Returns 3
size_t depth = GetDepth(v);
Cela doit être fait à au moment de la compilation, car cette profondeur sera transmise à la fonction de modèle en tant que paramètre de modèle:
// Same as calling foo<3>(v);
foo<GetDepth(v)>(v);
Y a-t-il un moyen de faire ça?
Un problème de modèle classique. Voici une solution simple comme la bibliothèque standard C++. L'idée de base est d'avoir un modèle récursif qui comptera une par une chaque dimension, avec un cas de base de 0 pour tout type qui n'est pas un vecteur.
#include <vector>
#include <type_traits>
template<typename T>
struct dimensions : std::integral_constant<std::size_t, 0> {};
template<typename T>
struct dimensions<std::vector<T>> : std::integral_constant<std::size_t, 1 + dimensions<T>::value> {};
template<typename T>
inline constexpr std::size_t dimensions_v = dimensions<T>::value; // (C++17)
Alors vous pouvez l'utiliser comme ceci:
dimensions<vector<vector<vector<int>>>>::value; // 3
// OR
dimensions_v<vector<vector<vector<int>>>>; // also 3 (C++17)
Éditer:
Ok, j'ai terminé l'implémentation générale pour tout type de conteneur. Notez que j'ai défini un type de conteneur comme tout ce qui a un type d'itérateur bien formé selon l'expression begin(t)
où std::begin
Est importé pour la recherche ADL et t
est une valeur l de type T
.
Voici mon code avec des commentaires pour expliquer pourquoi les choses fonctionnent et les cas de test que j'ai utilisés. Notez que cela nécessite C++ 17 pour être compilé.
#include <iostream>
#include <vector>
#include <array>
#include <type_traits>
using std::begin; // import std::begin for handling C-style array with the same ADL idiom as the other types
// decide whether T is a container type - i define this as anything that has a well formed begin iterator type.
// we return true/false to determing if T is a container type.
// we use the type conversion ability of nullptr to std::nullptr_t or void* (prefers std::nullptr_t overload if it exists).
// use SFINAE to conditionally enable the std::nullptr_t overload.
// these types might not have a default constructor, so return a pointer to it.
// base case returns void* which we decay to void to represent not a container.
template<typename T>
void *_iter_elem(void*) { return nullptr; }
template<typename T>
typename std::iterator_traits<decltype(begin(*(T*)nullptr))>::value_type *_iter_elem(std::nullptr_t) { return nullptr; }
// this is just a convenience wrapper to make the above user friendly
template<typename T>
struct container_stuff
{
typedef std::remove_pointer_t<decltype(_iter_elem<T>(nullptr))> elem_t; // the element type if T is a container, otherwise void
static inline constexpr bool is_container = !std::is_same_v<elem_t, void>; // true iff T is a container
};
// and our old dimension counting logic (now uses std:nullptr_t SFINAE logic)
template<typename T>
constexpr std::size_t _dimensions(void*) { return 0; }
template<typename T, std::enable_if_t<container_stuff<T>::is_container, int> = 0>
constexpr std::size_t _dimensions(std::nullptr_t) { return 1 + _dimensions<typename container_stuff<T>::elem_t>(nullptr); }
// and our Nice little alias
template<typename T>
inline constexpr std::size_t dimensions_v = _dimensions<T>(nullptr);
int main()
{
std::cout << container_stuff<int>::is_container << '\n'; // false
std::cout << container_stuff<int[6]>::is_container<< '\n'; // true
std::cout << container_stuff<std::vector<int>>::is_container << '\n'; // true
std::cout << container_stuff<std::array<int, 3>>::is_container << '\n'; // true
std::cout << dimensions_v<std::vector<std::array<std::vector<int>, 2>>>; // 3
}
En supposant qu'un conteneur est de tout type qui a value_type
et iterator
types de membres (les conteneurs de bibliothèque standard satisfont à cette exigence) ou un tableau de style C, nous pouvons facilement généraliser la solution de Cruz Jean:
template<class T, typename = void>
struct rank : std::integral_constant<std::size_t, 0> {};
// C-style arrays
template<class T>
struct rank<T[], void>
: std::integral_constant<std::size_t, 1 + rank<T>::value> {};
template<class T, std::size_t n>
struct rank<T[n], void>
: std::integral_constant<std::size_t, 1 + rank<T>::value> {};
// Standard containers
template<class T>
struct rank<T, std::void_t<typename T::iterator, typename T::value_type>>
: std::integral_constant<std::size_t, 1 + rank<typename T::value_type>::value> {};
int main() {
using T1 = std::list<std::set<std::array<std::vector<int>, 4>>>;
using T2 = std::list<std::set<std::vector<int>[4]>>;
std::cout << rank<T1>(); // Output : 4
std::cout << rank<T2>(); // Output : 4
}
Les types de conteneurs peuvent être davantage restreints si nécessaire.
Vous pouvez définir le modèle de classe suivant vector_depth<>
Qui correspond à n'importe quel type:
template<typename T>
struct vector_depth {
static constexpr size_t value = 0;
};
Ce modèle principal correspond au cas de base qui termine la récursivité. Ensuite, définissez sa spécialisation correspondante pour std::vector<T>
:
template<typename T>
struct vector_depth<std::vector<T>> {
static constexpr size_t value = 1 + vector_depth<T>::value;
};
Cette spécialisation correspond à un std::vector<T>
Et correspond au cas récursif.
Enfin, définissez le modèle de fonction, GetDepth()
, qui utilise le modèle de classe ci-dessus:
template<typename T>
constexpr auto GetDepth(T&&) {
return vector_depth<std::remove_cv_t<std::remove_reference_t<T>>>::value;
}
Exemple:
auto main() -> int {
int a{}; // zero depth
std::vector<int> b;
std::vector<std::vector<int>> c;
std::vector<std::vector<std::vector<int>>> d;
// constexpr - dimension determinted at compile time
constexpr auto depth_a = GetDepth(a);
constexpr auto depth_b = GetDepth(b);
constexpr auto depth_c = GetDepth(c);
constexpr auto depth_d = GetDepth(d);
std::cout << depth_a << ' ' << depth_b << ' ' << depth_c << ' ' << depth_d;
}
Le résultat de ce programme est:
0 1 2 3