【问题标题】:How to specialize methods for multiple types at once如何一次为多种类型专门化方法
【发布时间】:2017-03-23 15:38:48
【问题描述】:

我有这样的代码:

template< class T >
struct Value
{
    /* quite a lot of other functions which I do not want to specialize, too */
    void print( void );
};

template<> void Value<          short int >::print() { std::cout << "is integral" << std::endl; }
template<> void Value<                int >::print() { std::cout << "is integral" << std::endl; }
template<> void Value<           long int >::print() { std::cout << "is integral" << std::endl; }
template<> void Value< unsigned short int >::print() { std::cout << "is integral" << std::endl; }
template<> void Value< unsigned       int >::print() { std::cout << "is integral" << std::endl; }
template<> void Value< unsigned  long int >::print() { std::cout << "is integral" << std::endl; }

template<> void Value<       float >::print() { std::cout << "is floating point" << std::endl; }
template<> void Value<      double >::print() { std::cout << "is floating point" << std::endl; }
template<> void Value< long double >::print() { std::cout << "is floating point" << std::endl; }

template< class T > void Value<T>::print() { std::cout << "unsupported type" << std::endl; }

int main( void )
{
    Value< float  >().print();
    Value< double >().print();
    Value< short  >().print();
    Value<   char >().print();
}

输出:

is floating point
is floating point
is integral
unsupported type

我想更改它以减少代码重复,特别是因为代码主体比简单的std::cout 长得多。为了说明我想走的方向,想到的最简单的想法是使用宏:

#define TMP(T) \
template<> void Value<T>::print() { std::cout << "is integral" << std::endl; }
TMP(          short int )
TMP(                int )
TMP(           long int )
TMP( unsigned short int )
TMP( unsigned       int )
TMP( unsigned  long int )
#undef TMP
#define TMP(T) \
template<> void Value<T>::print() { std::cout << "is floating point" << std::endl; }
TMP(       float )
TMP(      double )
TMP( long double )
#undef TMP

但我想使用 C++11 模板魔法让它工作。我已经尝试过使用std::enable_if,但我就是无法让它工作。例如。这个

template< class T >
void Value<
    typename std::enable_if< std::is_integral<T>::value, T >::type
>::print( void )
{
    std::cout << "is integral" << std::endl;;
}

给我

test.cpp:26:24: error: invalid use of incomplete type ‘struct Value<typename std::enable_if<std::is_integral<_Tp>::value, T>::type>’
 >::type >::print( void )
                        ^
test.cpp:14:8: error: declaration of ‘struct Value<typename std::enable_if<std::is_integral<_Tp>::value, T>::type>’
 struct Value

并在返回类型上使用std::enable_if

template< class T >
typename std::enable_if< std::is_integral<T>::value, void >::type
Value<T>::print( void )
{
    std::cout << "is integral" << std::endl;;
}

给我:

test.cpp:41:1: error: prototype for ‘typename std::enable_if<std::is_integral<_Tp>::value, void>::type Value<T>::print()’ does not match any in class ‘Value<T>’
 Value<T>::print( void )
 ^
test.cpp:16:17: error: candidate is: static void Value<T>::print()
     static void print( void );

当然已经有很多类似的问题了:

但它们通常是关于简单函数的,而不是模板类的方法。而且他们通常不会像我想要的那样严格分开声明和定义。

This questions 听起来很相似,但它不是针对 struct/class 模板参数,而是针对另一个模板参数专门化该方法。

由于上述错误,我似乎无法将答案应用于我的具体问题。此外,我不想专门化整个类,因为该类本身共享许多对所有类型 T 都相同的方法。我不想用一个复制粘贴代码换另一个。

解释为什么会出现两条错误消息的奖励。我打破了什么规则。在我看来,...::type 似乎根本没有被 Tvoid 取代。

【问题讨论】:

标签: c++ c++11 templates


【解决方案1】:

您可以使用标签调度。定义你的标签:

namespace tag {

struct integral{};
struct floating{};
struct error{};

}

从各种类型定义它们的映射:

namespace detail
{

template<typename T, typename = void>
struct get_tag : tag::error{};

template<typename T>
struct get_tag<T, std::enable_if_t<std::is_integral<T>::value>> : tag::integral{};

template<typename T>
struct get_tag<T, std::enable_if_t<std::is_floating_point<T>::value>> : tag::floating{};

}

并为每个支持的标签定义你的功能:

void print(tag::error){
    std::cout << "unsupported type" << std::endl;
}
void print(tag::integral){
    std::cout << "is integral" << std::endl;
}
void print(tag::floating){
    std::cout << "is floating" << std::endl;
}

从您的静态方法转发给它时:

template< class T >
struct Value
{
    static void print( void ){
        ::print(detail::get_tag<T>{});
    }
};

demo

这可行,但您希望 char 不被视为一个整体,因此您可能想要定义自己的特征,即仅列出其中一个特征(更详细地解释 + C++11 版本 @ 987654322@):

template<typename T, typename... Others>
struct is_any : std::disjunction<std::is_same<T, Others>...>
{
};

现在您可以为您的积分标签编写以下内容

template<typename T>
struct get_tag<T, std::enable_if_t<
        is_any<T, short, int, long, unsigned short, unsigned, unsigned long>::value>> : tag::integral{};

结果会如你所愿。

demo


如果您想制作 prints 成员函数,可以将它们制作为模板以避免不支持的类型的编译错误:

template< class T >
struct Value
{
    static void print( void ){
        Value obj;
        obj.print(detail::get_tag<T>{});
    }
private:
    template<typename U = T>
    void print(tag::error){
        std::cout << "unsupported type" << std::endl;
    }
    template<typename U = T>
    void print(tag::integral){
        std::cout << "is integral" << std::endl;
    }
    template<typename U = T>
    void print(tag::floating){
        std::cout << "is floating" << std::endl;
    }

};

demo


或者,在 C++1z 中,您可以使用 constexpr if:

static void print( void ){
    using this_tag = detail::get_tag<T>;
    if constexpr(std::is_base_of<tag::floating, this_tag>::value) {
        std::cout << "is floating" << std::endl;
    } else if constexpr(std::is_base_of<tag::integral, this_tag>::value) {
        std::cout << "is integral" << std::endl;
    } else {
        std::cout << "unsupported type" << std::endl;
    }
}

demo

【讨论】:

  • 是否有可能使这个解决方案也适用于非静态成员函数?我的意思是,我必须将 print(tag:....) 定义为所有类型的(私有)成员,如果参数和成员变量类型不匹配,这可能会导致问题。
  • @mxmlnkn 检查编辑,我将在几秒钟内使用 C++1z 解决方案进行编辑
  • 只是小问题:从 c++14 开始,您可以定义 ``` template constexpr bool is_any_v= std::disjunction<:is_same>...>::值; ```
【解决方案2】:

要允许 SFINAE 用于您的结构,您必须添加额外的参数:

template<class T, typename AlwaysVoid = void>
struct Value;

然后

template<class T>
struct Value<T, std::enable_if_t<std::is_integral<T>::value>>
{
    void print()
    {
        std::cout << "is integral" << std::endl;;
    }
};

【讨论】:

  • 据我了解,这个解决方案使得有必要用许多其他函数重新定义整个结构。 I don't want to trade one copy-paste code for another.。或者你能让我看看我如何将它应用到:struct Value { /* many functions */ void print(); }
  • @mxmlnkn:确实你必须重新定义整个结构,但是如果你有通用代码,你可以使用继承来避免重复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-17
  • 1970-01-01
  • 2022-05-01
  • 1970-01-01
  • 2019-11-30
相关资源
最近更新 更多