SteelShot
SteelShot

Reputation: 125

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

Upvotes: 10

Views: 3894

Answers (6)

aschepler
aschepler

Reputation: 72356

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

Upvotes: 10

Picaud Vincent
Picaud Vincent

Reputation: 10982

If you need an extra template parameter U, as the other answers explained the right syntax is

template<typename T>
struct test
{
    template<typename U>
    ... a_method(...);
};

template<typename T> 
template<typename U>
... test<T>::a_method(...)
{
   ...
}

However in your peculiar case, if you only need to check some properties of the T type this is really an extra complication. Introduction of the U type is "artificial" and is only here because of the SFINAE

IMHO, it is much more elegant and simpler to use if constexpr

#include <iostream>
#include <type_traits>

template <typename T>
class Test
{
 public:
  void print();
};

template <typename T>
void Test<T>::print()
{
  if constexpr (std::is_arithmetic_v<T>)
  {
    std::cout << "\nOk T is arithmetic";
    // ... your implementation here ...
  }
  else
  {
    // throw an exception or do what ever you want,
    // here a compile-time error
    static_assert(!std::is_same_v<T, T>, "not implemented yet...");
  }
}
main()
{
  Test<int> t;
  t.print();

  Test<void> t2;
  // t2.print(); <- will generate a compile time error
}

Upvotes: 0

davidhigh
davidhigh

Reputation: 15488

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
}

Upvotes: 5

max66
max66

Reputation: 66210

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.

Upvotes: 4

SoronelHaetir
SoronelHaetir

Reputation: 15162

template<typename T>
struct test
{
    template<typename U = T>
    typename std::enable_if<std::is_arithmetic<U>::value>::type print();
};
template<typename T> template<typename U>
typename std::enable_if<std::is_arithmetic<U>::value>::type test<T>::print()
{
}
void foo()
{
    test<int> t;
    t.print();
    test<void*> u;
    u.print();
}

Upvotes: 0

Praetorian
Praetorian

Reputation: 109159

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

Upvotes: 1

Related Questions