Matt
Matt

Reputation: 22153

How do you specialize a member function inside a template class?

Let's say I have the following class:

template <typename T>
class SomeClass : Parent<T>
{
public:
    // I have a function such as this one:
    T DoSomething(const T &t)
    {
        return t.DoSomething(some_data);
    }

    // But `T` might be a pointer, so sometimes I will need something like the following
    // instead (which obviously doesn't work as given):
    T DoSomething(const T &t)
    {
        return new T(t->DoSomething(some_data));
    }

private:
    XYZ some_data;
};

I got stuck in a giant mess of template errors trying to implement this in any semi-nice way possible using template specialization.

In the end I came up with this very ugly solution:

template <typename T>
class SomeClass : Parent<T>
{
public:
    T DoSomething(const T &x)
    {
        return Specializer<T>::Do(this, x);
    }

private:
    template <typename V>
    struct Specializer {
        static V Do(SomeClass *me, const V &x)
        {
            return x.DoSomething(me->some_data);
        }
    };

    template <typename V>
    struct Specializer<V*> {
        static V* Do(SomeClass *me, const V *&x)
        {
            return new V(x->DoSomething(me->some_data));
        }
    };

    XYZ some_data;
};

Is there a better way to do this that doesn't involve stuffing this function into a dummy class/struct and passing around my this pointer?

PS: In reality, this has nothing to do with pointers, but rather with different types of containers. Pointers were just an easy example to use here.

Upvotes: 0

Views: 215

Answers (3)

Bitwize
Bitwize

Reputation: 11230

If you have access to , you can clean up the need for any SFINAE, specializations, or if constexpr by using concepts and constraints instead. This just allows you to define the same function N times with different criteria for its insantiation, which is much more readable IMO.

This is almost the same as the SFINAE approach, but without the need for the awful syntax (no std::declval, decltype, etc). It also doesn't require all implementations to exist in one function definition like the if constexpr approach; all you need is separate function definitions with different requires clauses:

#include <concepts>

...

template <typename T>
class SomeClass : Parent<T>
{
public:
    // Work for everything that's not specialized
    void DoSomething(const T &t) 
    { 
        std::cout << "Basic overload" << std::endl; 
    }

    // Only work for pointers
    void DoSomething(const T& t) requires(std::is_pointer_v<T>) 
    { 
        std::cout << "Pointer overload" << std::endl; 
    }

    // Only work if T is convertible to SomeType
    void DoSomething(const T& t) requires(std::convertible_to<T, SomeType>) 
    { 
        std::cout << "Convertible to SomeType overload" << std::endl; 
    }

private:
    XYZ some_data;
};

Live Example

In this approach there are 3 different entries:

  • The basic fallback for all templates
  • An implementation that works for any pointer type, and
  • An implementation that works for any T type that may be convertible to SomeType

Upvotes: 2

max66
max66

Reputation: 66230

What about using SFINAE?

For example

#include <utility>
#include <iostream>

template <typename>
struct Parent
 { };

using XYZ = int;

template <typename T>
class SomeClass : Parent<T>
 {
   public:
      template <typename U = T>
      auto DoSomething (T const & t)
       -> decltype( std::declval<U>().DoSomething(std::declval<XYZ>()) )
        { std::cout << "ref\n"; return t.DoSomething(some_data); }

      template <typename U = T>
      auto DoSomething (T const & t)
       -> std::remove_reference_t<
             decltype( std::declval<U>()->DoSomething(std::declval<XYZ>()),
                       std::declval<T>() )>
        {
          using V = std::remove_reference_t<decltype(*t)>;

          std::cout << "pnt\n"; return new V(t->DoSomething(some_data));
        }

   private:
      XYZ some_data;
 };

struct foo
 {
   foo (foo*) {}
   foo () {}

   foo DoSomething (int) const { return {}; }
 } ;

int main()
 {
   SomeClass<foo>   sc1;
   SomeClass<foo*>  sc2;

   foo f;

   sc1.DoSomething(f);
   sc2.DoSomething(&f);
 }

I mean: what about enabling the first version if, and only if, T is a type that supports a DoSomething(XYZ) method and enabling the second version if, and only if, T is a pointer of a type that supports a DoSomething(XYZ) method?

Upvotes: 1

cigien
cigien

Reputation: 60278

You can avoid writing any specializations, and use a type trait like std::is_pointer along with if constexpr to decide what code to execute depending on the whether the type is a pointer type or not:

auto DoSomething(const T &t)
{
  if constexpr (std::is_pointer_v<T>)
      return new T(t->DoSomething(some_data));
  else    
      return t.DoSomething(some_data);
}

If you don't want to check for whether T is a pointer, but want to check something else, you can still use this pattern by dropping in a suitable replacement for is_pointer.

Upvotes: 3

Related Questions