【问题标题】:restrict a template function, to only allow certain types限制模板函数,只允许某些类型
【发布时间】:2015-11-22 21:29:20
【问题描述】:

这里说我有一个简单的模板函数,原则上可以接受所有类型的类型:

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

我只想使用这个模板来计算几个类型,比如 std::vector 和 boost::array 对象。但是,每当我将 cout 用于其他类型甚至基本类型时,例如std::cout

我想问一下,是否可以限制我的模板功能,使其只接受我指定的几种类型?这就是当我使用 cout

更清楚地说,这就是我想要做的:

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.
}

【问题讨论】:

  • 问题是,我想包含 std::array, std::array, std::array, 等类型。 ..这是一个无限系列的不同类型..不确定是否有办法做到这一点..
  • 我仍然找不到答案,当我使用 cout
  • 检查每个有效类型 Type == TypeA or TypeB or TypeC 与为每个类型编写一个重载有何不同?

标签: c++ templates c++11


【解决方案1】:

为此编写一个真正通用的解决方案很难。检查任意类型Tstd::vectorstd::array 的问题在于后者不是类,它们是类模板。更糟糕的是,std::array 是一个带有非类型模板参数的类模板,所以你甚至不能拥有一个同时包含 std::vectorstd::array 的参数包。

你可以通过在类型中显式包装非类型参数来解决这个问题,但它变得丑陋、快速。

这是我想出的一个解决方案,它默认支持任何没有非类型模板参数的类或模板类。可以通过添加包装类型将非类型参数映射到类型参数来支持具有非类型模板参数的模板类。

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; 
}

请不要用这个,太可怕了。

Live Demo

【讨论】:

  • 创造了一个怪物是什么感觉? :) 我认为可以通过查看 ::&lt;&gt;... 的密度来预测阅读 C++ 时的局部心理痛苦程度。
【解决方案2】:

你可以像这样限制你的过载:

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); }

std::arrays 也一样(你应该用 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); }

或者,对于看起来更像您的编辑的解决方案,您可以使用 C++11 enable_if,尽管我个人厌恶它们,因为它们往往会使代码难以阅读和维护。所以我强烈推荐之前的解决方案。

// 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> }

【讨论】:

  • 我想写一个模板来处理几种不同的类型。模板的好处是我不必写出每个不同的实例化。
  • 这就是 C++ 的工作原理,你可以想要任何你喜欢的东西,但这并不意味着语言必须允许它。您想要的是可能的,但是您必须为每个 class 实现 一个 重载(即一个用于vectors,一个用于arrays,等等)。如果您只需要一种逻辑实现,您可以创建自己的函数my_output_stream 并从重载中调用它。事实上,有一个名为 Concept Lite 的 C++14 提案就是这样做的;允许为模板类型指定谓词。它被拒绝了,也许在未来的 C++ 版本中。
  • 我认为你可以用 C++11 做到这一点,但是处理普通类型和模板类型的必要性会让它变得很丑。
  • +1,每个类型都有明确的特征然后or将它们放在一起不是很可扩展,但它肯定比我implemented for fun的通用one_of特征更漂亮。
【解决方案3】:

使用 SFINAE 完成您的要求。

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;
}

但您最终可能只是为每种类型编写重载,而不是这样做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-06-03
    • 2012-01-30
    • 2018-10-13
    • 1970-01-01
    • 1970-01-01
    • 2012-06-05
    • 2022-01-05
    相关资源
    最近更新 更多