7

I give the following codes to show my question:

template<T>
void my_fun(T &obj)
{
  if(obj is a type like float, std::string, double)
   {
       perform1()
  }
  if(obj is a container like std::vector, std::list)
  {
      perform2()
 } 
}
std::vector<int> abc;
my_fun(abc);
int d;
my_fun(d);

Then my questions, how can I know the template refers to a simple type or a container? Thanks.

vsoftco
  • 55,410
  • 12
  • 139
  • 252
feelfree
  • 11,175
  • 20
  • 96
  • 167
  • Besides looking like a bad design and me recommending you look over the design instead, you could use [*type-traits*](http://en.cppreference.com/w/cpp/types#Type_traits_.28since_C.2B.2B11.29). You might need to implement specific type-traits that suits you though, like `is_container`. – Some programmer dude Jun 28 '16 at 13:13
  • 1
    `std::string` is of course a container of characters, so this question assumes a false dichotomy. – MSalters Jun 29 '16 at 11:56

4 Answers4

7

You can write your own trait and enable_if on it via expression SFINAE with multiple overloads. Here is a solution that uses the void_t trick (which will presumably appear in C++17):

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

template<typename ...>
using to_void = void; // maps everything to void, used in non-evaluated contexts

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

template<typename T>
struct is_container<T,
        to_void<decltype(std::declval<T>().begin()),
                decltype(std::declval<T>().end()),
                typename T::value_type
        >> : std::true_type // will  be enabled for iterable objects
{};

template<typename T>
void f(T param, typename std::enable_if<is_container<T>::value>::type* = nullptr)
{
    std::cout << "Container\n";
}

template<typename T>
void f(T param, typename std::enable_if<std::is_fundamental<T>::value>::type* = nullptr)
{
    std::cout << "Fundamental\n";
}

template<typename T>
void f(T param, 
    typename std::enable_if<!std::is_fundamental<T>::value>::type* = nullptr, 
    typename std::enable_if<!is_container<T>::value>::type* = nullptr)
{
    std::cout << "Other\n";
}

struct Foo{};

int main()
{
    int x{};            // fundamental
    std::vector<int> v; // container
    Foo s{};    // other

    f(x);
    f(v);
    f(s);
}

Live on Coliru

Community
  • 1
  • 1
vsoftco
  • 55,410
  • 12
  • 139
  • 252
2

You have several options at your disposal.

  • If you want a default behaviour, and change it only for one type (or a few), use template specialization for your function.

For example :

template<typename T>
void myfunc() { /*default*/ }


template<>
void myfunc<int>() { /*specialized version for int */}
  • If you want to change the behaviour of your functions for generic groups of types, you can use Type Traits (something like std::is_fundamental ). You might have to implement your own type traits in thiscase.
Louen
  • 3,617
  • 1
  • 29
  • 49
1

A (parametrized) container is a type. You can, however, overload it:

#include <iostream>
#include <vector>

template<typename T>
void my_fun(T &obj)
{
    perform1();
}

template<typename T>
void my_fun(std::vector<T> &obj)
{
    perform2();
}

int main(void)
{
    int              a;
    std::vector<int> b;

    my_fun(a);
    my_fun(b);
}

If this is not enough, you might also use std::enable_if<> so you don't need to write that part twice.

lorro
  • 10,687
  • 23
  • 36
1

my_fun can be implemented as following using SFINAE.

namespace details{
    struct A{};
    struct B:A{};

    // A container will have a begin and an end. Also make it first prerference
    template<typename T>
    auto my_fun_impl(T const & obj, B *) -> decltype( obj.begin(),obj.end(),void())
    {
        std::cout<<"Container\n";
    }

    // Default choice
    template<typename T>
    auto my_fun_impl(T const & obj,A*) -> void
    {
        std::cout<<"Other than Container\n";
    }
}
template<typename T>
auto my_fun(T const & obj) -> void
{
  details::my_fun_impl(obj,static_cast<details::B *>(0));
}

Note the passing a Base or Derived class pointer here, otherwise compiler will complain about ambiguous function definition.

Compiler will try to match exact signature of my_fun_impl with B pointer, it will succeed in case of container. Because a container will have begin() and end(), expected in trailing return type.

In case of non-container type first option will fail to match. And as we know that a Base class pointer can hold a derived class object, so default match will succeed.

And output of following test code

int main()
{
     my_fun(std::vector<int>{1,2,3});
     my_fun(1);
}

will be

Container
Other than Container

Demo on coliru

g-217
  • 2,069
  • 18
  • 33
  • [FYI] Not all containers supply a begin and end member functions. `std::queue` comes to me off the top of my head. – NathanOliver Jun 28 '16 at 13:36
  • @NathanOliver, FWIW, `std::queue` isn't a container, but a container adapter. – chris Jun 28 '16 at 13:37
  • @chris Ah yes. Then I think all actual containers do have begin and end. – NathanOliver Jun 28 '16 at 13:38
  • It might, however, be worth treating C arrays as containers by using `std::begin` and `std::end`. ADL shouldn't make a difference for this purpose. – chris Jun 28 '16 at 13:47