19

I can imagine the following code:

template <typename T> class X
{
  public:
   T container;

   void foo()
   {
      if(is_vector(T))
         container.push_back(Z);
      else
         container.insert(Z);
   }
}

// somewhere else...

X<std::vector<sth>> abc;
abc.foo();

How to write it, to successfully compile? I know type traits, but when I'm defining:

template<typename T> struct is_vector : public std::false_type {};

template<typename T, typename A>
struct is_vector<std::vector<T, A>> : public std::true_type {};

It doesn't compile:

error: no matching function for call to 'std::vector<sth>::insert(Z)'

static_assert also isn't that what I'm looking for. Any advices?

Here's a short example of what I want to achieve (SSCCE): http://ideone.com/D3vBph

m4tx
  • 4,139
  • 5
  • 37
  • 61
mkzwm
  • 495
  • 4
  • 18

6 Answers6

33

It is named tag dispatching :

#include <vector>
#include <set>
#include <type_traits>

template<typename T> struct is_vector : public std::false_type {};

template<typename T, typename A>
struct is_vector<std::vector<T, A>> : public std::true_type {};

template <typename T>
class X {
    T container;

    void foo( std::true_type ) {
        container.push_back(0);
    }
    void foo( std::false_type ) {
        container.insert(0);
    }
public:
    void foo() {
        foo( is_vector<T>{} );
    }
};

// somewhere else...
int main() {
    X<std::vector<int>> abc;
    abc.foo();

    X<std::set<int>> def;
    def.foo();
}
galop1n
  • 8,573
  • 22
  • 36
  • 1
    Perfectly that what I wanted and in addition simple. Thank you very much :) – mkzwm Feb 02 '14 at 16:28
  • 9
    Better that the dispatched overloads are private rather than public. – Puppy Feb 02 '14 at 16:29
  • 1
    @Puppy What is it for typename A here? template struct is_vector> : public std::true_type {}; Cant be just template struct is_vector> : public std::true_type {}; ? – Mert Mertce Jul 23 '15 at 10:31
  • @MertMertce: [`std::vector`](https://en.cppreference.com/w/cpp/container/vector) has also an `Allocator` as template parameter (which is defaulted). – Jarod42 Jun 30 '22 at 11:12
6

An alternative worth considering is to detect the presence of the push_back function using SFINAE. This is slightly more generic since it'll translate to other containers that implement push_back.

template<typename T>
struct has_push_back
{
    template<typename U>
    static std::true_type test(
        decltype((void(U::*)(const typename U::value_type&)) &U::push_back)*);

    template<typename>
    static std::false_type test(...);

    typedef decltype(test<T>(0)) type;
    static constexpr bool value = 
        std::is_same<type, std::true_type>::value;
};

Note that it currently only detects push_back(const T&) and not push_back(T&&). Detecting both is a little more complicated.

Here's how you make use of it to actually do the insert.

template<typename C, typename T>
void push_back_impl(C& cont, const T& value, std::true_type) {
    cont.push_back(value);
}

template<typename C, typename T>
void push_back_impl(C& cont, const T& value, std::false_type) {
    cont.insert(value);
}

template<typename C, typename T>
void push_back(C& cont, const T& value) { 
    push_back_impl(cont, value, has_push_back<C>::type());
}

std::vector<int> v;
push_back(v, 1);

std::set<int> s;
push_back(s, 1);

Honestly, this solution became a lot more complicated then I originally anticipated so I wouldn't use this unless you really need it. While it's not too hard to support const T& and T&&, it's even more arcane code that you have to maintain which is probably not worth it in most cases.

Ze Blob
  • 2,817
  • 22
  • 16
5

Using insert only:

#include <iostream>
#include <vector>
#include <set>

template <typename T>
class X
{
    public:
    T container;

    template <typename U>
    void insert(const U& u) {
        container.insert(container.end(), u);
    }
};

int main() {
    X<std::vector<int>> v;
    v.insert(2);
    v.insert(1);
    v.insert(0);

    for(std::vector<int>::const_iterator pos = v.container.begin();
        pos != v.container.end();
        ++pos)
    {
        std::cout << *pos;
    }
    std::cout << '\n';

    X<std::set<int>> s;
    s.insert(2);
    s.insert(1);
    s.insert(0);

    for(std::set<int>::const_iterator pos = s.container.begin();
        pos != s.container.end();
        ++pos)
    {
        std::cout << *pos;
    }
    std::cout << '\n';
}
  • The other container probably won't be a std::set and won't have a end() method. And if that'll be so simply I'm sure I'll write it without asking :P But +1 due to my unclear question. – mkzwm Feb 02 '14 at 17:09
1

Here's the typical method using void_t:

template <typename T>
using void_t = void;  // C++17 std::void_t

template <typename C, typename = void>  // I'm using C for "container" instead of T, but whatever.
struct has_push_back_impl : std::false_type {};

template <typename C>
struct has_push_back_impl<C, void_t<decltype(std::declval<C>().push_back(typename C::value_type{}))>>
    : std::true_type {};  // Note that void_t is technically not needed in this case, since the 'push_back' member function actually returns void anyway, but it the general method to pass the type into void_t's template argument to obtain void.  For example, the 'insert' function from std::set and std::map do NOT return void, so 'has_insert' will need to use void_t.

template <typename C>
using has_push_back = has_push_back_impl<C>;  // void passed to the second template argument by default, thus allowing the second specialization to be used instead of the primary template whenever C has a push_back member function.

This method will work for has_insert for associative containers, even though std::set, std::map's insert function return std::pair<typename T::iterator, bool> while std::multimap::insert returns std::multimap::iterator (this is one case where Ze Blob's method will not work).

prestokeys
  • 4,817
  • 3
  • 20
  • 43
1

If you use constexpr if, you were doing it right. This C++17 code compiles:

#include <iostream>
#include <type_traits>
#include <vector>
#include <list>

template<typename T> struct is_vector : public std::false_type {};

template<typename T, typename A>
struct is_vector<std::vector<T, A>> : public std::true_type {};


template <typename T>
class X
{
  public:
   T container;

   void foo()
   {
      if constexpr(is_vector<T>::value){
        std::cout << "I am manipulating a vector" << std::endl;
        // Can access container.push_back here without compilation error
      }
      else {
         std::cout << "I am manipulating something else" << std::endl;
      }
   }
};

int main() {
    X<std::vector<int>> abc;
    abc.foo(); // outputs "I am manipulating a vector"

    X<std::list<int>> def;
    def.foo(); // outputs "I am manipulating something else"
}
happy_sisyphus
  • 1,693
  • 1
  • 18
  • 27
0

in C++20 using requires expression:

#include <type_traits>
#include <concepts>
#include <vector>

template<class T>
static constexpr bool is_vector_v =  requires {
    requires std::same_as<std::decay_t<T>,
                          std::vector<typename std::decay_t<T>::value_type> >; 
};

and in code:

template<class T>
void foo() {
    if constexpr (is_vector_v<T>)
        container.push_back(Z);
    else
        container.insert(Z);
}
sk1a
  • 21
  • 4