如何在编译时获取多维std :: vector的深度?


45

我有一个函数,需要一个多维参数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);

有什么办法吗?


4
a的大小std::vector是运行时的内容,而不是编译时的内容。如果需要编译时大小的容器,请参阅std::array。也; 请记住,constexpr只表示“ 可以在编译时进行评估” -有没有承诺,这是。可以在运行时对其进行评估。
Jesper Juhl

5
@JesperJuhl,我不是在寻找大小,我正在寻找深度。两件截然不同的事情。我想知道std::vector相互嵌套的个数。例如,使用std::vector<std::vector<int>> v;GetDepth(v);将返回2,因为它是二维向量。大小无关紧要。
tjwrona1992 '19

4
半相关:嵌套vector并非总是做事的最佳方法。根据使用情况,手动对单个平面向量进行2d或3d索引编制可能会更有效。(只是整数数学,而不是从外部层次进行指针追赶。)
Peter Cordes

1
@PeterCordes更好的效率只是一个方面。另一个是平面类型可以更好地表示数组的连续性质。嵌套结构(可能具有不同的单个长度)从根本上说是一种类型不匹配,用于表示连续的n维超矩形。
康拉德·鲁道夫

4
命名法方面,标准库rank对数组类型进行此查询(与张量的数学命名法一致)。也许这比“深度”更好。
dmckee ---前主持人小猫,

Answers:


48

一个经典的模板问题。这是一个简单的解决方案,例如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

这是我的代码以及注释,用于解释为什么东西起作用以及我使用的测试用例。注意,这需要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 19/12/26

@ tjwrona1992是的,您可以直接复制并粘贴std::vector<T>专业化并将其更改为其他容器类型。您唯一需要的是您不擅长的任何类型的0基本情况
Cruz Jean

我的意思是没有复制/粘贴哈哈,就像模板模板一样
tjwrona1992 '19

@ tjwrona1992哦,为此,您需要使用SFINAE constexpr函数。我将为此原型制作一个东西并将其添加为编辑。
Cruz Jean

@ tjwrona1992,您对容器的定义是什么?
EVG

15

假设一个容器是具有任何类型value_typeiterator成员类型(标准库容器满足此要求)或C数组,我们可以很容易地推广克鲁兹吉恩的溶液:

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
}

如果需要,可以进一步限制容器类型。


这不适用于C样式的数组
Cruz Jean

1
@CruzJean,当然。这就是为什么我问什么是容器。无论如何,可以通过附加的专业知识轻松修复它,请参阅更新的答案。
EVG

2
@Evg谢谢。今天我了解了std :: void_t!辉煌!
marco6

2

您可以定义以下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;
};

此专业匹配an 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

1
这个作品std::vector,但如GetDepth(v)其中vint将无法编译。拥有GetDepth(const volatile T&)并返回会更好vector_depth<T>::valuevolatile只是让它涵盖更多的东西,成为最高的简历合格者
Cruz Jean

@CruzJean感谢您的建议。我已经编辑了答案。
眠りネロク
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.