Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 method template specialization for return type

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

like image 236
Daimon Avatar asked Nov 13 '14 18:11

Daimon


People also ask

How will you restrict the template for a specific datatype?

There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.

What is template specialization used for?

It is possible in C++ to get a special behavior for a particular data type. This is called template specialization. Template allows us to define generic classes and generic functions and thus provide support for generic programming.

Are template specializations inline?

An explicit specialization of a function template is inline only if it is declared with the inline specifier (or defined as deleted), it doesn't matter if the primary template is inline.

What is the difference between generic class template and specialization template?

Generics are generic until the types are substituted for them at runtime. Templates are specialized at compile time so they are not still parameterized types at runtime. The common language runtime specifically supports generics in MSIL.


3 Answers

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
    }
}
like image 93
Barry Avatar answered Sep 18 '22 04:09

Barry


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>{});
    }
}
like image 39
Mooing Duck Avatar answered Sep 22 '22 04:09

Mooing Duck


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

like image 25
Piotr Skotnicki Avatar answered Sep 20 '22 04:09

Piotr Skotnicki