Masalah templating klasik. Berikut adalah solusi sederhana seperti bagaimana perpustakaan standar C ++. Ide dasarnya adalah untuk memiliki template rekursif yang akan menghitung satu per satu setiap dimensi, dengan kasus dasar 0 untuk semua jenis yang bukan vektor.
#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)
Jadi Anda bisa menggunakannya seperti ini:
dimensions<vector<vector<vector<int>>>>::value; // 3
// OR
dimensions_v<vector<vector<vector<int>>>>; // also 3 (C++17)
Edit:
Ok, saya sudah menyelesaikan implementasi umum untuk semua jenis kontainer. Perhatikan bahwa saya mendefinisikan tipe kontainer sebagai segala sesuatu yang memiliki tipe iterator yang terbentuk dengan baik sesuai dengan ekspresi di begin(t)
mana std::begin
diimpor untuk pencarian ADL dan t
merupakan nilai jenisT
.
Berikut kode saya bersama dengan komentar untuk menjelaskan mengapa hal-hal berfungsi dan kasus uji yang saya gunakan. Catatan, ini membutuhkan C ++ 17 untuk dikompilasi.
#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
}
std::vector
adalah run-time thing, bukan compile-time. Jika Anda menginginkan wadah ukuran waktu kompilasi, lihatstd::array
. Juga; ingat bahwaconstexpr
hanya berarti " dapat dievaluasi pada waktu kompilasi" - tidak ada janji bahwa itu akan terjadi. Ini dapat dievaluasi pada saat run-time.