Srv19
Srv19

Reputation: 3618

Is there a way to ensure template argument is a container?

What are mechanisms for ensuring that template arguments are containers?

Alternatively, how can i make different specialisations for class/function depending on whether its argument is container or not?

Upvotes: 1

Views: 171

Answers (2)

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136485

Another way is to use boost::spirit::traits::is_container<>

Returns mpl::true_ if T has the following embedded types defined: value_type, iterator, size_type, and reference. Otherwise it will return mpl::false_.

Works in C++03 and above.

Upvotes: 1

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275800

Testing for a full blown container is difficult and ambiguous. Personally, I consider something a container if and only if it owns its immediate contents, but this may not line up with other people's use of the term.

Testing for iterability is neither difficult nor ambiguous, and is often what you want to test for when taking an object in a function argument. There are iterable things that aren't very container like (C++1y's or C++1z's string_view, as an example).

In my opinion, in C++11, an instance c of a type X is iterable iff:

for( auto&& a : c ) {}

is well formed. The wording chosen for the above means that you can extend any type with begin and end free function overloads to make it iterable.

A decent approximation if the above will work would be to test if, in an argument dependent enabled lookup context, that std::begin and std::end return something that has a valid std::iterator_traits<>.

As a quick sketch, I get something like this:

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

namespace aux {
  using std::begin;
  // note: no implementation
  template<typename C>
  auto adl_begin( C&& c )->decltype( begin( std::forward<C>(c) ) );
  using std::end;
  // note: no implementation
  template<typename C>
  auto adl_end( C&& c )->decltype( end( std::forward<C>(c) ) );
}
template<typename T>
struct is_iterable<T,
  typename std::enable_if<
    std::is_same<
      typename std::iterator_traits< typename std::decay<decltype( aux::adl_begin( std::declval<T>() ) )>::type >::iterator_category,
      typename std::iterator_traits< typename std::decay<decltype( aux::adl_end( std::declval<T>() ) )>::type >::iterator_category
    >::value
  >::type
> : std::true_type {};

Live example

Containers, meanwhile, rarely are as uniform. Their most basic "common" processes, like adding elements, differ from one to another in ways that differ in signature and semantics. erase(iterator) and ::allocator_type are the two most common features of container-like structures, but a std::array<T,N> and T[N] are arguably containers and have neither property.

In short, beyond their common property of iterability, containers differ too much for there to be a highly useful is_container<C> traits class. Instead, you should work out what properties of the container you are looking for (ability to delete elements? Ability to insert elements? Random access?) and test for those instead.

Upvotes: 1

Related Questions