Derleme zamanında çok boyutlu bir std :: vektörünün derinliğini nasıl alabilirim?


45

Çok boyutlu alır std::vectorve bir şablon parametresi olarak geçirilmesi için derinlik (veya boyut sayısı) gerektiren bir işlevi var . Bu değeri kodlamak yerine derinliği bir değer olarak constexpralıp std::vectordöndürecek bir fonksiyon yazmak istiyorum unsigned integer.

Örneğin:

std::vector<std::vector<std::vector<int>>> v =
{
    { { 0, 1}, { 2, 3 } },
    { { 4, 5}, { 6, 7 } },
};

// Returns 3
size_t depth = GetDepth(v);

Bu derleme zamanında yapılmalıdır, çünkü bu derinlik şablon işlevine bir şablon parametresi olarak iletilecektir:

// Same as calling foo<3>(v);
foo<GetDepth(v)>(v);

Bunu yapmanın bir yolu var mı?


4
A'nın boyutu std::vectorderleme zamanı değil, çalışma zamanıdır. Derleme zamanı büyüklüğünde bir kap istiyorsanız, konusuna bakın std::array. Ayrıca; unutmayın constexprtek yolu " olabilir derleme zamanında değerlendirilecek" - o hiçbir söz yoktur edecek olması. Çalışma zamanında değerlendirilebilir.
Jesper Juhl

5
@JesperJuhl, beden aramıyorum, derinlik arıyorum. Çok farklı iki şey. Kaç tane std::vectoriç içe iç içe olduğunu bilmek istiyorum . Örneğin std::vector<std::vector<int>> v;, GetDepth(v);2 boyutlu bir vektör olduğu için 2 değerini döndürür. Boyut ilgisiz.
tjwrona1992

4
Yarı ilgili: yuvalanmış vectorher zaman bir şeyler yapmanın en iyi yolu değildir. Tek bir düz vektörün manuel 2d veya 3d indekslenmesi, kullanım durumuna bağlı olarak daha verimli olabilir. (Dış seviyelerden işaretçi kovalamak yerine tamsayı matematik.)
Peter Cordes

1
@PeterCordes Daha iyi verimlilik sadece bir özelliktir. Bir diğeri, düz tipin dizinin bitişik doğasını daha iyi temsil etmesidir. Yuvalanmış bir yapı (potansiyel olarak farklı bireysel uzunluklarda) temel olarak bitişik, n boyutlu bir hiper-dikdörtgeni temsil eden bir tür uyuşmazlığıdır.
Konrad Rudolph

4
İsimlendirmede standart kütüphane rankbu sorgu için dizi tiplerinde kullanır (tensörler için matematiksel isimlendirmeye uygun olarak). Belki de bu "derinlik" ten daha iyi bir kelime.
dmckee --- eski moderatör yavru kedi

Yanıtlar:


48

Klasik bir şablonlama problemi. İşte C ++ standart kütüphanesinin yaptığı gibi basit bir çözüm. Temel fikir, her boyutta birer sayılacak özyinelemeli bir şablona sahip olmaktır.

#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)

O zaman bu şekilde kullanabilirsiniz:

dimensions<vector<vector<vector<int>>>>::value; // 3
// OR
dimensions_v<vector<vector<vector<int>>>>; // also 3 (C++17)

Düzenle:

Tamam, herhangi bir konteyner türü için genel uygulamayı bitirdim. Ayrıca ifade göre biçimlendirilmiş bir yineleyici türü olan herhangi bir şey gibi bir veri yapısı tanımlanan Not ADL arama için aktarılır ve tipte bir lvalue olup .begin(t)std::begintT

İşte kodum ve işlerin neden çalıştığını ve kullandığım test senaryolarını açıklayan yorumlar. Not, bu derlemek için C ++ 17 gerektirir.

#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
}

Bunun sadece vektörler için değil, tüm iç içe geçmiş kaplarda çalışmasını istersem ne olur? Bunu yapmanın kolay bir yolu var mı?
tjwrona1992

@ tjwrona1992 Ya, sadece std::vector<T>uzmanlığı kopyalayıp yapıştırabilir ve başka bir kap türüne değiştirebilirsiniz. İhtiyacınız olan tek şey, uzmanlaşmadığınız herhangi bir tür için 0 temel durumdur
Cruz Jean

Kopyalamak / yapıştırmak
istemiyordum

@ tjwrona1992 Oh, bunun için SFINAE constexpr işlevlerini kullanmanız gerekir. Bunun için bir şey prototiplendireceğim ve onu düzenleme olarak ekleyeceğim.
Cruz Jean

@ tjwrona1992, bir konteyner tanımınız nedir?
Evg

15

Bir kabın herhangi bir tür value_typeve iteratorüye türü (standart kütüphane kapları bu gereksinimi karşılar) veya C stili bir dizi olduğunu varsayarsak, Cruz Jean'in çözümünü kolayca genelleştirebiliriz :

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
}

Konteyner türleri gerekirse daha da kısıtlanabilir.


Bu C stili diziler için geçerli değildir
Cruz Jean

1
@CruzJean, tabi. Bu yüzden bir konteynerin ne olduğunu sordum. Neyse, ek bir uzmanlık ile kolayca düzeltilebilir, güncellenmiş cevaba bakın.
Evg

2
@Evg teşekkür ederim. Bugün std :: void_t! Parlak!
marco6

2

vector_depth<>Herhangi bir türle eşleşen aşağıdaki sınıf şablonunu tanımlayabilirsiniz :

template<typename T>
struct vector_depth {
   static constexpr size_t value = 0;
};

Bu birincil şablon, özyinelemeyi sonlandıran temel duruma karşılık gelir. Ardından, ilgili uzmanlığını aşağıdakiler için tanımlayın std::vector<T>:

template<typename T>
struct vector_depth<std::vector<T>> {
   static constexpr size_t value = 1 + vector_depth<T>::value;
};

Bu uzmanlık bir ile eşleşir std::vector<T>ve özyinelemeli duruma karşılık gelir.

Son olarak, GetDepth()yukarıdaki sınıf şablonuna uyan fonksiyon şablonunu tanımlayın :

template<typename T>
constexpr auto GetDepth(T&&) {
   return vector_depth<std::remove_cv_t<std::remove_reference_t<T>>>::value;
}

Misal:

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;
}

Bu programın çıktısı:

0 1 2 3

1
Bunun için çalışmalar std::vectorancak örneğin GetDepth(v)nerede volduğunu intderlemek olmaz. Sahip olmak GetDepth(const volatile T&)ve sadece geri daha iyi olurdu vector_depth<T>::value. volatilemaksimum cv nitelikli olmak üzere daha fazla şeyi kapsamasına izin veriyor
Cruz Jean

@CruzJean Öneri için teşekkürler. Cevabı düzenledim.
ク り ネ ロ ク
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.