CodeBricks
CodeBricks

Reputation: 1801

How to tell if something is a container?

I want to determine if something is a container. Right now I'm testing it on containers that are formatted to have a type and allocator. My ultimate goal is to learn how to write a container template that, when containing layers of itself can allow direct iteration on its innermost elements. E.g. if there was a container of 3 containers each containing 3 more containers, each containing 3 elements, I want to be able to iterate through all 27 elements in one range based for loop. Distinguishing whether something is a container that directly contains elements or contains containers of elements (or containers of containers...of containers of elements) using SFINAE to check whether the element has an iterator seems like a logical first step towards determining whether the begin() function should return the iterator to the element or the result of said element's begin() function. If I can get my first step to work, I'd like to include that logic into writing my container, but I can't get it to compile:

#include <vector>
#include <iostream>

template <typename T,
    template <typename E, typename Allocator = std::allocator<E>> class Container
>
void is_cont(typename Container<T>::iterator it)
{
    std::cout << "is iterator\n";
}

template <typename T,
    template <typename E, typename Allocator = std::allocator<E>> class Container
>
void is_cont(Container<T>& cont)
{
    std::cout << "is container\n";
}

int main()
{
    std::vector<int> vec{ 2, 4, 6 };
    is_cont(vec);               // Output: "is container"
    //is_cont(vec.begin());     // COMPILER ERROR
}

How can I fix this?

Upvotes: 3

Views: 798

Answers (1)

Sam Varshavchik
Sam Varshavchik

Reputation: 118352

Your definition of whether something is or is not a container appears to be based solely on whether it has an iterator inner class.

That's as good of a heuristic as any, I suppose, but let's go with that.

It most instances, SFINAE is much simpler if it's done with classes, instead of trying to implement SFINAE with a function, like your is_container(). Making your is_container() into a class results in a following, textbook SFINAE solution:

#include <vector>
#include <string>
#include <iostream>
#include <type_traits>

template<typename ...>
using void_t=void;

template<typename T, class=void> struct is_container : std::false_type {};

template<typename T>
struct is_container<T, void_t<typename T::iterator>> : std::true_type {};

int main()
{
    std::cout << is_container<int>::value << std::endl;

    std::cout << is_container<std::vector<int>>::value << std::endl;
    return 0;
}

Now, a related question would be whether there's a better way to check if something is a container, or not. Whichever heuristic you choose, it's trivial to adjust a class-based test, instead of a function-based. For example, if you want to consider whether something is a container if it has both iterator and const_iterator inner classes, just need to change one line:

template<typename T>
struct is_container<T, void_t<typename T::iterator,
                              typename T::const_iterator>> : std::true_type {};

Upvotes: 6

Related Questions