5

Here say I have a simple template function that in principle can accept all kind of types:

template <class Type> std::ostream& operator<< (std::ostream& stream, const Type subject) { stream << "whatever, derived from subject\n"; return stream; } 

I only want to use this template to cout a few types, say std::vector and boost::array objects. However whenever I use cout to other types even elementary types, e.g. std::cout << int(5);, will be a compilation error, because there are two possible implementations of operator<<(std::ostream, int) now, one is in standard c++, the other specified by my template function.

I would like to ask, is it possible to restrict my template function, so that it only accepts a few types specified by me? That is how to tell the compiler to ignore my template when i use cout << int(5). Thanks in advance.

To be more clear, this is what I want to do:

template <class Type> std::ostream& operator<< (std::ostream& stream, const Type subject) { if (Type == TypeA or TypeB or TypeC) //use this template and do these {...}; else //ignore this template, and use operator<< provided in standard c++ library. } 
4
  • 1
    possible duplicate of C++ templates that accept only certain types Commented Aug 28, 2015 at 9:10
  • The problem is, I want to include types such as std::array<int, 2>, std::array<int,3>, std::array<int,4>, ... and that's an infinite series of different types.... not sure if there is a way to do this.. Commented Aug 28, 2015 at 9:14
  • still I cannot find an answer, how to prevent the compiler from instantiation from my template function, when i use cout << int(5). The static_assert or BOOST_STATIC_ASSERT will generate a compile error, instead of ignoring my template, when the assertion fails. Commented Aug 28, 2015 at 10:04
  • How is checking each valid type Type == TypeA or TypeB or TypeC different from writing one overload for each? Commented Aug 28, 2015 at 10:15

3 Answers 3

6

Writing a really generic solution for this is hard. The problem with checking an arbitrary type T against std::vector or std::array is that the latter are not classes, they are class templates. Even worse, std::array is a class template with a non-type template parameter, so you can't even have a parameter pack which will hold both std::vector and std::array.

You can get around this somewhat by explicitly wrapping non-type parameters up in types, but it gets ugly, fast.

Here is a solution I came up with that will support any class or template class with no non-type template parameters by default. Template classes with non-type template parameters can be supported by adding a wrapper type to map non-type parameters to type parameters.

namespace detail{ //checks if two types are instantiations of the same class template template<typename T, typename U> struct same_template_as: std::false_type {}; template<template<typename...> class X, typename... Y, typename... Z> struct same_template_as<X<Y...>, X<Z...>> : std::true_type {}; //this will be used to wrap template classes with non-type args template <typename T> struct wrapImpl { using type = T; }; //a wrapper for std::array template <typename T, typename N> struct ArrayWrapper; template <typename T, std::size_t N> struct ArrayWrapper<T, std::integral_constant<std::size_t, N>> { using type = std::array<T,N>; }; //maps std::array to the ArrayWrapper template <typename T, std::size_t N> struct wrapImpl<std::array<T,N>> { using type = ArrayWrapper<T,std::integral_constant<std::size_t,N>>; }; template <typename T> using wrap = typename wrapImpl<typename std::decay<T>::type>::type; //checks if a type is the same is one of the types in TList, //or is an instantiation of the same template as a type in TempTList //default case for when this is false template <typename T, typename TList, typename TempTList> struct one_of { using type = std::false_type; }; //still types in the first list to check, but the first one doesn't match template <typename T, typename First, typename... Ts, typename TempTList> struct one_of<T, std::tuple<First, Ts...>, TempTList> { using type = typename one_of<T, std::tuple<Ts...>, TempTList>::type; }; //type matches one in first list, return true template <typename T, typename... Ts, typename TempTList> struct one_of<T, std::tuple<T, Ts...>, TempTList> { using type = std::true_type; }; //first list finished, check second list template <typename T, typename FirstTemp, typename... TempTs> struct one_of<T, std::tuple<>, std::tuple<FirstTemp, TempTs...>> { //check if T is an instantiation of the same template as first in the list using type = typename std::conditional<same_template_as<wrap<FirstTemp>, T>::value, std::true_type, typename one_of<T, std::tuple<>, std::tuple<TempTs...>>::type>::type; }; } //top level usage template <typename T, typename... Ts> using one_of = typename detail::one_of<detail::wrap<T>,Ts...>::type; struct Foo{}; struct Bar{}; template <class Type> auto operator<< (std::ostream& stream, const Type subject) //is Type one of Foo or Bar, or an instantiation of std::vector or std::array -> typename std::enable_if<   one_of<Type, std::tuple<Foo,Bar>, std::tuple<std::vector<int>,std::array<int,0>> >::value, std::ostream&>::type { stream << "whatever, derived from subject\n"; return stream; } 

Please don't use this, it's horrible.

Live Demo

Sign up to request clarification or add additional context in comments.

1 Comment

How does it feel to have created a monster? :) I think one could predict the amount of local mental pain reading C++ by looking at the density of ::, <> and ....
4

You can restrict your overload like this:

template <class T> std::ostream& my_private_ostream( std::ostream& stream, const T& data ) { <your implementation> } template <class T, class A> std::ostream& operator<< ( std::ostream& stream, const std::vector<T,A>& data ) { return my_private_ostream(stream,data); } 

Same for std::arrays (you should tag your question with c++11):

template <class T, size_t N> std::ostream& operator<< ( std::ostream& stream, const std::array<T,N>& data ) { return my_private_ostream(stream,data); } 

Alternatively, for a solution that looks a bit more like your edit, you could use C++11 enable_if, although I have a personal aversion to them as they tend to make the code difficult to read and maintain. So I strongly recommend the previous solution.

// Vector type predicate template <class T> struct is_vector: std::false_type {}; template <class T, class A> struct is_vector< std::vector<T,A> >: std::true_type {}; // Array type predicate template <class T> struct is_array: std::false_type {}; template <class T, size_t N> struct is_array< std::array<T,N> >: std::true_type {}; // The overload with the syntax you want template <class Indexable> typename std::enable_if< is_vector<Indexable>::value || is_array<Indexable>::value, std::ostream& >::type operator<< ( std::ostream& stream, const Indexable& data ) { <your implementation> } 

4 Comments

I want to write one template to be able to handle a few different types. The good thing of template is that I don't have to write out each different instantiation.
This is how C++ works, you can want anything you like but it doesn't mean the language has to allow it. What you want is possible, but you'll have to implement one overload per class (ie one for vectors, one for arrays, etc). If you want only one implementation of your logic, you can create your own function my_output_stream and call it from the overloads. As a matter of fact, there was a proposal for C++14 called Concept Lite which did just this; allow to specify predicates for template types. It was rejected, maybe in a future version of C++.
I think you can do this with C++11, but the necessity to handle normal types and template types will make it pretty ugly.
+1, having explicit traits for each type then oring them together is not very extensible, but it's sure prettier than the generic one_of trait which I implemented for fun.
1

Use SFINAE to do what you're asking.

template<typename...> struct is_vector: std::false_type{}; template<typename T, typename Alloc> struct is_vector<std::vector<T, Alloc>>: std::true_type{}; template<typename...> struct is_array: std::false_type{}; template<typename T, std::size_t Size> struct is_array<std::array<T, Size>>: std::true_type{}; template<typename T> struct is_my_ostream_type{ enum { value = is_vector<T>::value || is_array<T>::value }; }; template< typename T, typename = typename std::enable_if<is_my_ostream_type<T>::value>::type > std::ostream &operator <<(std::ostream &lhs, const T &rhs){ lhs << "is my special ostream overload"; return lhs; } 

But you're probably going to end up just writing an overload for every type rather than doing this.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.