Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specialize methods for multiple types at once

I have code like this:

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

Output:

is floating point
is floating point
is integral
unsupported type

I want to change this in order to reduce code duplication, especially, because the code body is much longer than a simple std::cout. To illustrate the direction I want to go, the simplest idea coming to mind would be to use macros:

#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

But I want to get it to work using C++11 template magic. I already tried using std::enable_if, but I just can't get it to work. E.g. this

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

gives me

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

and using std::enable_if on the return type:

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

gives me:

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

Of course there are many similar questions already:

  • http://www.cplusplus.com/forum/beginner/151879/
  • match multiple types for template specialization resolution
  • Template specialization for multiple types
  • How to do one explicit specialization for multiple types?

But they often are about simple functions, not methods of templated classes. Also they often don't strictly separate declaration and definition as I want to.

This questions sounds very similar, but it specializes the method not in respect to the struct/class template argument, but in respect to another template argument.

I just can't seem to apply the answers to my specific problem, because of the aforementioned errors. Also I don't want to specialize the whole class, because the class itself shares many methods which are identical for all types T. I don't want to trade one copy-paste code for another.

Bonus for explaining why the two error messages happen. What rule am I breaking. To me it seems that ...::type isn't substituted at all with T or void.

like image 916
mxmlnkn Avatar asked Nov 09 '16 18:11

mxmlnkn


2 Answers

You could use tag dispatch. Define your tags:

namespace tag {

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

}

Define their mapping from various types:

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

}

And define your function for each supported tag:

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

While forwarding to it from your static method:

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

demo

This will work, but you want char not to be treated as an integral, so you may want to define your own trait, that only one of the listed traits (explained in more detail + C++11 version here):

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

Now you can write the following for your integral tag

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

and the result will be exactly as you wanted.

demo


If your want to make your prints member functions, you can make them templates to avoid compile errors for unsupported types:

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


Alternatively, in C++1z you can use 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

like image 191
krzaq Avatar answered Nov 13 '22 01:11

krzaq


To allow SFINAE for your struct, you have to add extra parameter:

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

And then

template<class T>
struct Value<T, std::enable_if_t<std::is_integral<T>::value>>
{
    void print()
    {
        std::cout << "is integral" << std::endl;;
    }
};
like image 2
Jarod42 Avatar answered Nov 13 '22 00:11

Jarod42