restrict a template function, to only allow certain types

前端 未结 3 1784
暗喜
暗喜 2020-12-18 06:05

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

template 
std::ostream& operator<< (st         


        
相关标签:
3条回答
  • 2020-12-18 06:35

    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.

    0 讨论(0)
  • 2020-12-18 06:41

    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

    0 讨论(0)
  • 2020-12-18 06:48

    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> }
    
    0 讨论(0)
提交回复
热议问题