Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

function implementation with enable_if outside of class definition

So basically, I have a very basic generic class for now, currently testing the type_traits header. I am currently trying to make a function to work with certain types i.e arithmetic ones for now.

#include <type_traits>

template <typename T> class Test {
    public:
        template <typename U = T> 
        typename std::enable_if<std::is_arithmetic<U>::value>::type print();
}; 

The function works perfectly and for arithmetic types only.

But I like to keep my classes tidy and only have them have prototypes, while the function implementations are outside of the class.

With standard templates i.e

void test();

template <typename T> void Test<T>::test() {}

It is simple and I know how to, but I have no clue how to declare the implementation outside of the class with "std::enable_if" and every attempt I have made, during compilation says that that the prototype does not match any in the class.

I have managed to find a similar question here but the class there is standard and not generic.

PS. I am using MinGW-w64 with -std=c++17

like image 644
SteelShot Avatar asked Jan 27 '18 20:01

SteelShot


4 Answers

You need one set of template parameters for the class template and one separate set of template parameters for the member function template. You need to repeat the entire complicated return type, since it's part of the function template signature. And note you cannot repeat the default argument =T, or the compiler will think you're trying to define it twice (without checking whether or not the new definition is identical).

template <typename T> template <typename U>
typename std::enable_if<std::is_arithmetic<U>::value>::type
Test<T>::print()
{
    // Implementation here.
}

By the way, you're using the "long way" of writing the type, as was needed in C++11. But C++14 introduced a std::enable_if_t shortcut, and C++17 introduced a std::is_arithmetic_v shortcut. So if you're using C++17, you can also write the type

typename std::enable_if<std::is_arithmetic<U>::value>::type

as just

std::enable_if_t<std::is_arithmetic_v<U>>
like image 61
aschepler Avatar answered Oct 13 '22 10:10

aschepler


If you put the enable_if in the default template parameter, which is imo nicer anyway, the out-of-class definition becomes a bit easier:

template<typename T>
struct Test
{
    template <typename S = T
            , typename = typename std::enable_if<std::is_arithmetic<S>::value>::type >
    void print();
};

template<typename T>
template<typename S, typename>
void Test<T>::print()
{
    //some code
}
like image 36
davidhigh Avatar answered Oct 13 '22 11:10

davidhigh


You can try with

template <typename T>
template <typename U>
std::enable_if_t<std::is_arithmetic<U>::value> Test<T>::print()
 { /* do something */ }

The following is a full working example

#include <iostream>
#include <type_traits>

template <typename T> class Test
 {
   public:
      template <typename U = T>
      std::enable_if_t<std::is_arithmetic<U>::value> print();
 }; 

template <typename T>
template <typename U>
std::enable_if_t<std::is_arithmetic<U>::value> Test<T>::print()
 { std::cout << "test!" << std::endl; }

int main ()
 {
   Test<int>  ti;
   Test<void> tv;

   ti.print();   // compile
   //tv.print(); // compilation error
 }

Off Topic 1

Observe that your solution can be hijacked in this way

Test<void>{}.print<int>(); 

To avoid this problem you could impose that T is equal to U,

template <typename T> class Test
 {
   public:
      template <typename U = T>
      std::enable_if_t<   std::is_arithmetic<U>::value
                       && std::is_same<T, U>::value> print()
       { }
 }; 

Off Topic 2

As you can see, you have to repeat the SFINAE part (std::enable_if_t, std::is_arithmetic and std::is_same).

Taking in count that you have to repeat the implementation in an header, I don't think (IMHO) that to write the implementation of template classes outside the body of the class is a great idea.

like image 4
max66 Avatar answered Oct 13 '22 09:10

max66


Since you haven't posted what you attempted I can't tell you where you went wrong. But this is how you would implement the member function outside the class definition (although it still needs to be implemented in the header, so I don't think this is worth the trouble)

template <typename T> class Test {
    public:
        template <typename U = T>
        typename std::enable_if<std::is_arithmetic<U>::value>::type print();
};

template <typename T>  // class template parameter
template <typename U>  // function template parameter
inline typename std::enable_if<std::is_arithmetic<U>::value>::type Test<T>::print()
{
}

Live demo

like image 1
Praetorian Avatar answered Oct 13 '22 11:10

Praetorian