多次元std::vector
を受け取り、深さ(または次元の数)をテンプレートパラメーターとして渡す必要がある関数があります。この値をハードコーディングする代わりに、std::vector
を受け取り、深度をunsigned integer
値として返すconstexpr
関数を記述します。
例えば:
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);
これを行う方法はありますか?
古典的なテンプレートの問題。以下は、C++標準ライブラリが行うような簡単なソリューションです。基本的な考え方は、各次元を1つずつカウントする再帰的なテンプレートを作成することです。ベクトルではないすべての型の基本ケースは0です。
_#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)
_
したがって、次のように使用できます。
_dimensions<vector<vector<vector<int>>>>::value; // 3
// OR
dimensions_v<vector<vector<vector<int>>>>; // also 3 (C++17)
_
編集:
さて、私はあらゆるコンテナタイプの一般的な実装を終えました。 begin(t)
という式に従って、整形式のイテレータタイプを持つものとしてコンテナタイプを定義したことに注意してください。ここで、ADLルックアップ用に_std::begin
_がインポートされ、t
はT
と入力します。
これが、コードが機能する理由と使用したテストケースを説明するコメントと一緒のコードです。これをコンパイルするには、C++ 17が必要です。
_#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
}
_
コンテナがvalue_type
およびiterator
メンバータイプ(標準ライブラリコンテナーはこの要件を満たす)またはCスタイルの配列、簡単に一般化できます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
}
必要に応じて、コンテナタイプをさらに制限できます。
次のクラステンプレート_vector_depth<>
_を定義して、任意のタイプに一致させることができます。
_template<typename T>
struct vector_depth {
static constexpr size_t value = 0;
};
_
このプライマリテンプレートは、再帰を終了する基本ケースに対応しています。次に、対応する_std::vector<T>
_の特殊化を定義します。
_template<typename T>
struct vector_depth<std::vector<T>> {
static constexpr size_t value = 1 + vector_depth<T>::value;
};
_
この特殊化は_std::vector<T>
_に一致し、再帰的なケースに対応します。
最後に、上記のクラステンプレートを使用する関数テンプレートGetDepth()
を定義します。
_template<typename T>
constexpr auto GetDepth(T&&) {
return vector_depth<std::remove_cv_t<std::remove_reference_t<T>>>::value;
}
_
例:
_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;
}
_
このプログラムの出力は次のとおりです。
_0 1 2 3
_