C++11 method template specialization for return type

1.4k Views Asked by At

I've got following class:

class Foo {
 public:
  template <typename T>
  T bar() {
      cout << "Called with return type: " << typeid(T).name() << endl;

      T t = //... (some implementation here)

      return t;
  }
}

It's invoked in following way:

Foo foo;
int i = foo.bar<int>();
long l = foo.bar<long>();

Now i'd like to have different specialization for cases when function is invoked with shared_ptr<T>

Foo foo;
foo.bar<shared_ptr<int>>();
foo.bar<shared_ptr<long>>();

But of course I don't want to create full specialization for each type. Is it possible to implement such behaviour (can be trait-based if required)?

3

There are 3 best solutions below

6
On BEST ANSWER

Since noone proposed it yet, one can use SFINAE to distinguish between T and std::shared_ptr<U>:

template <typename T>
struct is_shared_ptr_impl : std::false_type {};
template <typename T>
struct is_shared_ptr_impl<std::shared_ptr<T>> : std::true_type {};
template <typename T>
using is_shared_ptr = typename is_shared_ptr_impl<typename std::decay<T>::type>::type;

class Foo
{
public:    
    template <typename T>
    auto bar()
        -> typename std::enable_if<!is_shared_ptr<T>{}, T>::type
    {
        std::cout << "T is " << typeid(T).name() << std::endl;
        return {};
    }

    template <typename T>
    auto bar()
        -> typename std::enable_if<is_shared_ptr<T>{}, T>::type
    {
        using U = typename std::decay<T>::type::element_type;
        std::cout << "T is shared_ptr of " << typeid(U).name() << std::endl;
        return {};
    }
};

DEMO

6
On

There's certainly many ways to do it. The first way that comes to my mind is simply function overloading. Since you don't have a parameter to overload on, you'll have to make one. I like pointers, which effectively act as a way to pass types to functions.

class Foo {

    //regular overload 
    template<typename T>
    T bar(T*) { //takes a pointer with an NULL value
      cout << "Called with return type: " << typeid(T).name() << endl;

      T t = //... (some implementation here)

      return t;
    }
    //shared_ptr overload - NOTE THAT T IS THE POINTEE, NOT THE SHARED_PTR
    template<typename T>
    std::shared_ptr<T> bar(std::shared_ptr<T>*) { //takes a pointer with an null value
      cout << "Called with return type: " << typeid(T).name() << endl;

      std::shared_ptr<T> t = //... (some implementation here)

      return t;
    }

public:
    template <typename T>
    T bar() {
        T* overloadable_pointer = 0;
        return bar(overloadable_pointer);
    }
};

I've never heard of anyone else using pointers to pass types around, so if you choose to do this, comment thoroughly, just to be safe. It is wierd code.

It may be more intuitive to simply use a helper struct to do template specialization, which is what most people would do. Unfortunately, if you need access to the members of Foo (which you presumably do), using template specialization would require you to pass all those members to the function, or friend the template helpers. Alternatively, you could pass a type_traits specialization thing to another member, but that ends up simply being a complex version of the pointer trick above. Many find it more normal and less confusing though, so here's that:

template<typename T>
struct Foo_tag {};

class Foo {
    //regular overload 
    template<typename T>
    T bar(Foo_tag<T>) {
    }
    //shared_ptr overload - NOTE THAT T IS THE POINTEE, NOT THE SHARED_PTR
    template<typename T>
    std::shared_ptr<T> bar(Foo_tag<std::shared_ptr<T>>) {
    }

public:
    template <typename T>
    T bar() {
        return bar(Foo_tag<T>{});
    }
}
5
On

You cannot partially specialize functions. For a story on why, check out this GOTW.

You can partially specialize classes though, so what you could do is:

template <typename T>
T bar() {
    return bar_impl<T>::apply(this);
}

Where:

template <typename T>
struct bar_impl {
    static T apply(Foo* ) {
        // whatever
    }
}

template <typename T>
struct bar_impl<std::shared_ptr<T>> {
    static std::shared_ptr<T> apply(Foo* ) {
        // whatever else
    }
}