10

I am looking for a way to provide a function that takes a templated (STL) container, but requires its elements to be of a certain type (e.g. int).

These function calls should be VALID:

std::vector<int> Argument;
void foo( Argument );

std::list<int> Argument
void foo( Argument );

std::deque<int> Argument
void foo( Argument );

...etc

These function calls should be INVALID:

std::vector<float> Argument;
void foo( Argument );

std::list<double> Argument
void foo( Argument );

std::deque<char> Argument
void foo( Argument );

...etc

Is there a way to template "foo" such that containers of int are accepted, but containers with different element types are not accepted ?

Best, Ben

quantdev
  • 23,517
  • 5
  • 55
  • 88
S.H
  • 875
  • 2
  • 11
  • 27

5 Answers5

13

Use the Standard Library semantics :

  • Pass a pair of iterators to foo, not a container : it makes your function much more generic
  • Use std::iterator_traits<Iterator>::value_type to get to the value type
  • static_assert the value type of the Iterator to be an int (or whatever type you want)

Example :

#include <list>
#include <vector>

template<typename Iterator>
void foo(Iterator begin, Iterator end)
{
    static_assert(std::is_same<int, typename std::iterator_traits<Iterator>::value_type>::value, 
                                "Invalid value type : must be int");
}

int main() {
    std::list<int> l1;
    std::vector<int> v1;

    foo(std::begin(l1), std::end(l1)); // OK
    foo(std::begin(v1), std::end(v1)); // OK

    std::vector<float> v2;
    foo(std::begin(v2), std::end(v2)); // Doesn't compile
}

Live demo

Note:

  • If foo needs to access specific member functions of the container (as noted by Deduplicator, this might happen for performance reasons), then you might need to stick with the Container argument :

Example : (Note the difference for getting at the value_type, as pointed by MooingDuck, this is required to make it work with arrays):

template <typename Container>
void foo(const Container& c)
{

    static_assert(std::is_same<int, std::iterator_type<decltype(std::begin(c))>::value_type>::value, "Invalid value type : must be int");

   // Use c member function(s)
}
quantdev
  • 23,517
  • 5
  • 55
  • 88
9

STL containers have the typedef value_type, so you may use it.

Then you may forbid with static_assert:

template <typename Container>
void foo(const Container& )
{
    static_assert(std::is_same<int, typename Container::value_type>::value, "expect int type");
}

or via SFINAE

template <typename Container>
typename std::enable_if<std::is_same<int, typename Container::value_type>::value>::type
foo(const Container& )
{
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Could you give an explanation, why/when this is to be favored over a purely template-based solution for the problem? – S.H Aug 20 '14 at 20:25
  • @S.H: `static_assert` give a better error message and SFINAE disables the non matching template and so is not part of overload (so you may add an other overload for float container for example). – Jarod42 Aug 20 '14 at 20:29
4

You can simply use Sfinae, like this:

#include <type_traits>
#include <utility>

template <typename Container>
typename std::enable_if< std::is_same< typename Container::value_type, int >::value,
void >::type foo(Container& c) {
  /* code here */
};

Which will be removed from the set of overloads of "foo" if the container does not have a value type that is the same as "int". You can also use other traits, like is_convertible for being more accepting of related types. Calling foo with a container that does not have int as values will be reported by the compiler has having no suitable overload candidates.

If you don't have C++11 support, the things I used above are available in Boost as a C++98/03 alternative.

Mikael Persson
  • 18,174
  • 6
  • 36
  • 52
3

Another solution, using template template parameters

template<template<typename, typename...> class Container, typename... Params>
void foo(Container<int, Params...> const&)
{
  ...
}

This will match vector, list or deque as long the type of the elements in the container is int.

Live demo

Praetorian
  • 106,671
  • 19
  • 240
  • 328
0

Assumeing you want an iterable range:

template<class I>
using value_type_t=typename std::iterator_traits<I>::value_type;

Obsolete in C++14:

template<class T>using decay_t=typename std::decay<T>::type;
template<bool b,class T=void>using enable_if_t=typename std::enable_if<b,T>::type;

Then:

namespace adl_details{
  using std::begin;
 template<class C>using iterator_type=decay_t<decltype(begin(std::declval<C>()))>;
}
using adl_details::iterator_type;


template<class C>
enable_if_t<std::is_same<int, value_type_t<iterator_type_t<C>>>::value>
foo(C&& c){
}

has that property.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524