Ben Jones
Ben Jones

Reputation: 957

Specializing a template class member function by type

I'm trying to specialize a member function on a templated class by a type trait of it's template parameter, but my forward declaration is apparently incorrect. Is there an easy fix?

#include <type_traits>

template <typename T>
class TTest{
public:
  T data;

  // edited to comment this out, template<typename U>
  bool operator !=(const TTest& other) const;
};

template <typename T>
typename std::enable_if<std::is_unsigned<T>::value, bool>::type
TTest<T>::operator !=(const TTest<T>& other) const{
  return true;
}

template<typename T>
bool TTest<T>::operator !=(const TTest<T>& other) const{
  return false;
}

int main(){
  TTest<size_t> t1;
  TTest<int> t2;
}

Clang tells me:

templateTest.cpp:13:11: error: out-of-line definition of 'TTest::operator!='
  differs from the declaration in the return type
TTest<T>::operator !=(const TTest<T>& other) const{
          ^
templateTest.cpp:8:8: note: previous declaration is here
  bool operator !=(const TTest& other) const;
       ^
1 error generated.

Upvotes: 0

Views: 264

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275936

Tag dispatching is the clean way to do this:

template <typename T>
class TTest{
  bool not_equal( const ITest& other, std::true_type /* is unsigned */ ) const;
  bool not_equal( const ITest& other, std::false_type /* is unsigned */ ) const;
public:
  T data;

  bool operator !=(const TTest& other) const {
    return not_equal( other, std::is_unsigned<T>() );
  }
};

now simply implement the two TTest<T>::not_equal overloads. Only the one that is actually called for a given T will be compiled past basic parsing.

Upvotes: 1

Praetorian
Praetorian

Reputation: 109289

It seems like the whole enable_if shebang is part of the function signature (or I don't really understand the errors). I can get the code to compile and behave as you want if I change it to

template <typename T>
class TTest{
public:
  T data;

  template<typename U>
  typename std::enable_if<std::is_unsigned<U>::value, bool>::type
  operator !=(const TTest<U>& other) const;

  template<typename U>
  typename std::enable_if<not std::is_unsigned<U>::value, bool>::type
  operator !=(const TTest<U>& other) const;
};

template <typename T>
template <typename U>
typename std::enable_if<std::is_unsigned<U>::value, bool>::type
TTest<T>::operator !=(const TTest<U>&) const{
  return true;
}

template <typename T>
template <typename U>
typename std::enable_if<not std::is_unsigned<U>::value, bool>::type
TTest<T>::operator !=(const TTest<U>&) const{
  return false;
}

Live demo. Of course, this gets a lot less repetitive if you define those operators inline.

A better approach might be to dispatch to different private implementations of the operator logic based on the traits of T. This removes all the SFINAE verbosity from your code.

template <typename T>
class TTest{
public:
  T data;

  bool operator!=(const TTest& other) const
  {
      return not_equal_to(other, typename std::is_unsigned<T>::type());
  }

private:
  bool not_equal_to(TTest const&, std::true_type) const
  {
      return true;
  }

  bool not_equal_to(TTest const&, std::false_type) const
  {
      return false;
  }
};

Live demo

Upvotes: 4

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 154035

You declared a member function template of you class template and you are trying to specialize it as a member function. That's not going to fly. Also, the return types differ although end up evaluating to the same thing.

I don't know what you are trying to attempt as the type U of the member function template isn't even deduced (did you mean the argument to be of type TTest<U>?). If you want to specialize your member based on a trait, I think you'll either need t overload the operator or use a different approach (e.g., delegating the implementation to a specialized class template):

#include <iostream>
#include <type_traits>

template <typename T>
class TTest{
public:
  T data;

  template<typename U>
  typename std::enable_if<!std::is_unsigned<U>::value, bool>::type
  operator !=(const TTest<U>& other) const;

  template<typename U>
  typename std::enable_if<std::is_unsigned<U>::value, bool>::type
  operator !=(const TTest<U>& other) const;
};

template <typename T>
template<typename U>
typename std::enable_if<!std::is_unsigned<U>::value, bool>::type
TTest<T>::operator !=(const TTest<U>& other) const {
    return true;
}

template <typename T>
template<typename U>
typename std::enable_if<std::is_unsigned<U>::value, bool>::type
TTest<T>::operator !=(const TTest<U>& other) const {
    return false;
}

int main(){
  TTest<unsigned int> t1;
  TTest<int> t2;
  std::cout << std::boolalpha
            << "t1 != t1: " << (t1 != t1) << '\n'
            << "t1 != t2: " << (t1 != t2) << '\n'
            << "t2 != t1: " << (t2 != t1) << '\n'
            << "t2 != t2: " << (t2 != t2) << '\n';
}

Upvotes: 1

Related Questions