How can I find out that a C ++ template is a container or type?

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 are, how can I find out if a template refers to a simple type or container? Thank you

+7
c ++ stl templates
source share
4 answers

You have several options at your disposal.

  • If you want the default behavior and change it for only one type (or several), use the 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 behavior of your functions for generic type groups, you can use Type Traits (something like std::is_fundamental ), you may have to implement your own type traits in this case.
+2
source share

You can write your own tag and enable_if on it using the SFINAE expression with several overloads. Here is a solution that uses void_t trick (which is supposed to 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

+3
source share

A (parameterized) container is a type. However, you can 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 can also use std::enable_if<> , so you do not need to write this part twice.

+1
source share

my_fun can be implemented as follows 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)); } 

Pay attention to passing a pointer to the Base or Derived class here, otherwise the compiler will complain about the ambiguous definition of the function.

The compiler will try to match the exact signature of my_fun_impl with the B pointer , this will succeed in the case of the container. Since the container will have begin () and end (), it is expected in the end type of the return value.

In the absence of the first type of container, the type will not match. And as we know, a Base class pointer can contain an object of a derived class, so the default match will succeed.

And the output of the following test code

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

will be

 Container Other than Container 

Demo at coliru

+1
source share

All Articles