How can I get the depth of a multidimensional std::vector at compile time?

前端 未结 3 453
鱼传尺愫
鱼传尺愫 2021-02-01 01:37

I have a function that takes a multidimensional std::vector and requires the depth (or the number of dimensions) to be passed in as a template parameter. Instead of

3条回答
  •  南旧
    南旧 (楼主)
    2021-02-01 02:10

    A classic templating problem. Here's a simple solution like how the C++ standard library does. The basic idea is to have a recursive template that will count one by one each dimension, with a base case of 0 for any type that is not a vector.

    #include 
    #include 
    
    template
    struct dimensions : std::integral_constant {};
    
    template
    struct dimensions> : std::integral_constant::value> {};
    
    template
    inline constexpr std::size_t dimensions_v = dimensions::value; // (C++17)
    

    So then you could use it like so:

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

    Edit:

    Ok, I've finished the general implementation for any container type. Note that I defined a container type as anything that has a well-formed iterator type as per the expression begin(t) where std::begin is imported for ADL lookup and t is an lvalue of type T.

    Here's my code along with comments to explain why stuff works and the test cases I used. Note, this requires C++17 to compile.

    #include 
    #include 
    #include 
    #include 
    
    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
    void *_iter_elem(void*) { return nullptr; }
    template
    typename std::iterator_traits::value_type *_iter_elem(std::nullptr_t) { return nullptr; }
    
    // this is just a convenience wrapper to make the above user friendly
    template
    struct container_stuff
    {
        typedef std::remove_pointer_t(nullptr))> elem_t;    // the element type if T is a container, otherwise void
        static inline constexpr bool is_container = !std::is_same_v; // true iff T is a container
    };
    
    // and our old dimension counting logic (now uses std:nullptr_t SFINAE logic)
    template
    constexpr std::size_t _dimensions(void*) { return 0; }
    
    template::is_container, int> = 0>
    constexpr std::size_t _dimensions(std::nullptr_t) { return 1 + _dimensions::elem_t>(nullptr); }
    
    // and our nice little alias
    template
    inline constexpr std::size_t dimensions_v = _dimensions(nullptr);
    
    int main()
    {
        std::cout << container_stuff::is_container << '\n';                 // false
        std::cout << container_stuff::is_container<< '\n';               // true
        std::cout << container_stuff>::is_container << '\n';    // true
        std::cout << container_stuff>::is_container << '\n';  // true
        std::cout << dimensions_v, 2>>>; // 3
    }
    

提交回复
热议问题