C ++ how to distinguish template for container and native type

I have the following problem:

template<class T> void set(std::string path, const T data) { stringstream ss; ss << data << std::endl; write(path, ss.str(); } template<class T> void set(std::string path, const T data) { std::stringstream ss; for(typename T::const_iterator it = data.begin(); it < data.end(); ++it) { ss << *it; if(it < data.end() -1 ) ss << ", "; } ss << std::endl; write(path, ss.str()); } 

I get the following error:

 error: 'template<class T> void myclass::set(std::string, T)' cannot be overloaded error: with 'template<class T> void myclass::set(std::string, T)' 

Is there a way to distinguish between container types and other types in templates?

+6
source share
4 answers

In C ++ 03, you can do this with a little SFINAE to selectively enable different versions of a function for different types:

 #include <boost/type_traits.hpp> #include <sstream> #include <iostream> #include <vector> using namespace std; template<class T> void set(typename boost::enable_if<boost::is_pod<T>, std::string>::type path, const T data) { std::cout << "POD" << std::endl; stringstream ss; ss << data << std::endl; } template<class T> void set(typename boost::disable_if<boost::is_pod<T>, std::string>::type path, const T data) { std::cout << "Non-POD" << std::endl; std::stringstream ss; for(typename T::const_iterator it = data.begin(); it < data.end(); ++it) { ss << *it; if(it < data.end() -1 ) ss << ", "; } ss << std::endl; } int main() { int i; float f; std::vector<int> v; set("", v); set("", i); set("", f); } 

I used boost for convenience here, but you can roll on your own if boost is not an option or uses C ++ 11 instead.

is_pod not quite what you really want, you probably need the is_container trait, but what is not so trivial, you need to make your own trait and is_pod gives a good approximation for using signs to selectively include functions as a simple answer.

+6
source

Use the tag:

 #include <type_traits> template <typename T> typename std::enable_if<is_container<T>::value>::type set (std::string const & path, T const & container) { // for (auto const & x : container) // ... } template <typename T> typename std::enable_if<!is_container<T>::value>::type set (std::string const & path, T const & data) { std::ostringstream oss; oss << data; write(path, oss.str()); } 

You can find a suitable trait in good printer code .

+8
source

Here you can try the replacement methods Failure Is Not Error (SFINAE).

First, you need a function to determine if the type has an iterator member ...

 template <typename T> struct Has_Iterator { template <typename> static char test(...); template <typename U> static int test(typename U::const_iterator*); static const bool result = sizeof test<T>(0) != sizeof(char); }; 

In the above code, the C ++ standard needs to use test(typename U::const_iterator*) to prefer the undefined parameter "..." if U is actually a struct / class with the element type const_iterator , otherwise you have "replacement failure" is not a fatal error that stops compilation (hence SFINAE), and trying to find the appropriate function is saturated with test(...) . As the return types of the two functions differ, the sizeof operator can check which one has been matched by setting the result boolean value accordingly.

Then you can redirect print requests to specialized templates that support them ...

 template <typename T> void print(const T& data) { printer<Has_Iterator<T>::result, T>()(data); } // general case handles types having iterators... template <bool Has_It, typename T> struct printer { void operator()(const T& data) { for (typename T::const_iterator i = data.begin(); i != data.end(); ++i) std::cout << *i << ' '; std::cout << '\n'; } }; // specialisation for types lacking iterators... template <typename T> struct printer<false, T> { void operator()(const T& data) { std::cout << data << '\n'; } }; 
+1
source

As my predecessors wrote, you should use some kind of trait. You should probably use Boost for this, but if you don't want to, you can just use something like this ( http://ideone.com/7mAiB ):

 template <typename T> struct has_const_iterator { typedef char yes[1]; typedef char no[2]; template <typename C> static yes& test(typename C::const_iterator*); template <typename> static no& test(...); static const bool value = sizeof(test<T>(0)) == sizeof(yes); }; template <bool> class bool2class {}; template <class T> void set_inner(const std::string &path, T & var, bool2class<false> *) { // T is probably not STL container } template <class T> void set_inner(const std::string &path, T & var, bool2class<true> *) { // T is STL container } template <class T> void set(const std::string &path, T &var) { set_inner(path, var, (bool2class<has_const_iterator<T>::value>*)0); } 

It is not an easy task to distinguish a container from a simple array, so I used a check here if the type has const_iterator . You should probably also check to see if it has begin() , end() and other things that you will use in future code.

0
source

Source: https://habr.com/ru/post/920869/


All Articles