如何在编译时获得多维 std::vector 的深度?
Posted
技术标签:
【中文标题】如何在编译时获得多维 std::vector 的深度?【英文标题】:How can I get the depth of a multidimensional std::vector at compile time? 【发布时间】:2020-04-16 19:56:08 【问题描述】:我有一个函数,它采用多维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
仅表示“可能在编译时被评估”——没有保证它会。它可以在运行时进行评估。
@JesperJuhl,我不是在寻找尺寸,而是在寻找深度。两种截然不同的东西。我想知道有多少std::vector
s 相互嵌套。例如std::vector<std::vector<int>> v;
,GetDepth(v);
将返回 2,因为它是一个二维向量。大小无关紧要。
半相关:嵌套vector
并不总是最好的做事方式。单个平面向量的手动 2d 或 3d 索引可能更有效,具体取决于用例。 (只是整数数学而不是从外部级别追逐指针。)
@PeterCordes 提高效率只是一方面。另一个是扁平类型更好地代表了数组的连续性。嵌套结构(可能具有不同的个体长度)基本上是表示连续的 n 维超矩形的类型不匹配。
命名法方面,标准库使用rank
来查询数组类型(与张量的数学命名法一致)。也许这比“深度”更好。
【参考方案1】:
一个经典的模板问题。这是一个简单的解决方案,就像 C++ 标准库所做的那样。基本思想是有一个递归模板,每个维度都会一个一个地计数,对于任何不是向量的类型,基本情况都是 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)
将容器类型定义为具有格式正确的迭代器类型的任何东西,其中 std::begin
是为 ADL 查找导入的,t
是 T
类型的左值。
这是我的代码以及 cmets,用于解释为什么这些东西有效以及我使用的测试用例。注意,这需要 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
【讨论】:
如果我希望它适用于所有嵌套容器而不仅仅是向量怎么办?有没有一种简单的方法可以做到这一点? @tjwrona1992 是的,您可以直接复制并粘贴std::vector<T>
特化并将其更改为其他容器类型。您唯一需要的是 0 基本情况,适用于您不专攻的任何类型
我的意思是没有复制/粘贴哈哈,就像模板模板
@tjwrona1992 哦,为此您需要使用 SFINAE constexpr 函数。我将为此制作一个原型并将其添加为编辑。
@tjwrona1992,你对容器的定义是什么?【参考方案2】:
假设容器是具有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
如果需要,可以进一步限制容器类型。
【讨论】:
@CruzJean,当然。这就是为什么我问什么是容器。无论如何,它可以通过额外的专业化轻松修复,请参阅更新的答案。 @Evg 谢谢。今天我了解了std::void_t!太棒了!【参考方案3】:您可以定义以下匹配任何类型的类模板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
【讨论】:
这适用于std::vector
,但例如GetDepth(v)
其中v
是int
不会编译。最好有GetDepth(const volatile T&)
并返回vector_depth<T>::value
。 volatile
只是让它覆盖更多的东西,最大的简历合格
@CruzJean 感谢您的建议。我已经编辑了答案。以上是关于如何在编译时获得多维 std::vector 的深度?的主要内容,如果未能解决你的问题,请参考以下文章
如何使std :: vector的operator []编译在DEBUG中进行边界检查,但不在RELEASE中进行
如何使 std::vector 的 operator[] 编译在 DEBUG 中而不是在 RELEASE 中进行边界检查