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.
}
yu quan
  • 161
  • 1
  • 1
  • 14
  • 1
    possible duplicate of [C++ templates that accept only certain types](http://stackoverflow.com/questions/874298/c-templates-that-accept-only-certain-types) – Christophe Weis Aug 28 '15 at 09:10
  • The problem is, I want to include types such as std::array, std::array, std::array, ... and that's an infinite series of different types.... not sure if there is a way to do this.. – yu quan Aug 28 '15 at 09: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. – yu quan Aug 28 '15 at 10:04
  • How is checking each valid type `Type == TypeA or TypeB or TypeC` different from writing one overload for each? – Jonathan H Aug 28 '15 at 10:15

3 Answers3

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

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • 5
    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 `...`. – Jonathan H Aug 28 '15 at 11:44
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> }
Jonathan H
  • 7,591
  • 5
  • 47
  • 80
  • 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. – yu quan Aug 28 '15 at 10:00
  • 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 `vector`s, one for `array`s, 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++. – Jonathan H Aug 28 '15 at 10:07
  • 1
    I think you *can* do this with C++11, but the necessity to handle normal types and template types will make it pretty ugly. – TartanLlama Aug 28 '15 at 10:17
  • +1, having explicit traits for each type then `or`ing them together is not very extensible, but it's sure prettier than the generic `one_of` trait which I [implemented for fun](http://coliru.stacked-crooked.com/a/78f4097a1659487c). – TartanLlama Aug 28 '15 at 10:46
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.

RamblingMad
  • 5,332
  • 2
  • 24
  • 48