17

In C++ if you want to partially specialize a single method in a template class you have to specialize the whole class (as stated for example in Template specialization of a single method from templated class with multiple template parameters)

This however becomes tiresome in bigger template classes with multiple template parameters, when each of them influences a single function. With N parameters you need to specialize the class 2^N times!

However, with the C++11 I think there might a more elegant solution, but I am not sure how to approach it. Perhaps somehow with enable_if? Any ideas?

Community
  • 1
  • 1
CygnusX1
  • 20,968
  • 5
  • 65
  • 109

5 Answers5

13

In addition to the inheritance-based solution proposed by Torsten, you could use std::enable_if and default function template parameters to enable/disable certain specializations of the function.

For example:

template<typename T>
struct comparer
{
    template<typename U = T ,
    typename std::enable_if<std::is_floating_point<U>::value>::type* = nullptr>
    bool operator()( U lhs , U rhs )
    {
        return /* floating-point precision aware comparison */;
    }

    template<typename U = T ,
    typename std::enable_if<!std::is_floating_point<U>::value>::type* = nullptr>
    bool operator()( U lhs , U rhs )
    {
        return lhs == rhs;
    } 
};

We take advantage of SFINAE to disable/enable the different "specializations" of the function depending on the template parameter. Because SFINAE can only depend on function parameters, not class parameters, we need an optional template parameter for the function, which takes the parameter of the class.

I prefer this solution over the inheritance based because:

  • It requires less typing. Less typing probably leads to less errors.
  • All specializations are written inside the class. This way to write the specializations holds all of the specializations inside the original class , and make the specializations look like function overloads, instead of tricky template based code.

But with compilers which have not implemented optional function template parameters (Like MSVC in VS2012) this solution does not work, and you should use the inheritance-based solution.

EDIT: You could ride over the non-implemented-default-function-template-parameters wrapping the template function with other function which delegates the work:

template<typename T>
struct foo
{
private:
    template<typename U>
    void f()
    {
        ...
    }

public:
    void g()
    {
        f<T>();
    }
};

Of course the compiler can easily inline g() throwing away the wrapping call, so there is no performance hit on this alternative.

CygnusX1
  • 20,968
  • 5
  • 65
  • 109
Manu343726
  • 13,969
  • 4
  • 40
  • 75
  • 1
    As per discussion in http://stackoverflow.com/questions/14600201/why-to-avoid-stdenable-if-in-function-signatures, you can put enable_if as a second method template argument (after U=T) rather than the function return type. This makes function signature cleaner and should work with constructors too. – CygnusX1 Jan 17 '14 at 12:15
  • @CygnusX1 besides I know that way, I always use the return type way. However, that solution is more generic (Works with ctors as you said), so I don't know why I always prefer the return type way ... :) – Manu343726 Jan 17 '14 at 12:58
4

One solution would be to forward from the function, you want to overload to some implementation that depends on the classes template arguments:

template < typename T >
struct foo {
   void f();
};

template < typename T >
struct f_impl {
    static void impl()
    {
        // default implementation
    }
};

template <>
struct f_impl<int> {
    static void impl()
    {
         // special int implementation
    }
};

template < typename T >
void foo< T >::f()
{
    f_impl< T >::impl();
}

Or just use private functions, call them with the template parameter and overload them.

template < typename T >
class foo {
public:
    void f()
    {
        impl(T());
    }
private:
    template < typename G >
    void impl( const G& );
    void impl( int );
};

Or if it's really just one special situation with a very special type, just query for that type in the implementation.

Torsten Robitzki
  • 3,041
  • 1
  • 21
  • 35
  • This gets a bit nasty when ::impl() refers to the main class - you need to pass 'this' pointer and make the side-struct a friend. Also, the existence of fields may somehow depend on T (that's why you want to specialize). If you just overload all function variants in private, they may fail to compile for some T. – CygnusX1 Jan 17 '14 at 12:22
  • Yes, the first solution has it's limits. In the second solution, you don't have to overload for all possible template class parameters, the first impl() function acts like catch all default. If you need different 'fields' depending on the template class parameters, the 'usual' solution is, to put them in a base class and to fully specialize that base class. But that's somehow out of the scope of the original question. – Torsten Robitzki Jan 17 '14 at 12:57
4

With enable_if:

#include <iostream>
#include <type_traits>

template <typename T>
class A {
    private:
    template <typename U>
    static typename std::enable_if<std::is_same<U, char>::value, char>::type
    g() {
        std::cout << "char\n";
        return char();
    }

    template <typename U>
    static typename std::enable_if<std::is_same<U, int>::value, int>::type
    g() {
        std::cout << "int\n";
        return int();
    }

    public:
    static T f() { return g<T>(); }
};

int main(void)
{
    A<char>::f();
    A<int>::f();
    // error: no matching function for call to ‘A<double>::g()’
    // A<double>::f();
    return 0;
}
  • I think you have forgotten to add the default value of `U` template parameter. Else your calls should be `A::f();` which is meaningless. – Manu343726 Jan 17 '14 at 10:52
  • 2
    @Manu343726 f is delegating to g (no default is needed) –  Jan 17 '14 at 11:01
  • mmm I have never thought about wrapping the function template to not depend on optional template parameters. Thanks – Manu343726 Jan 17 '14 at 11:04
3

Tag dispatching is often the clean way to do this.

In your base method, use a traits class to determine what sub version of the method you want to call. This generates a type (called a tag) that describes the result of the decision.

Then perfect forward to that implememtation sub version passing an instance of the tag type. Overload resolution kicks in, and only the implememtation you want gets instantiated and called.

Overload resolution based on a parameter type is a much less insane way of handling the dispatch, as enable_if is fragile, complex at point of use, gets really complex if you have 3+ overloads, and there are strange corner cases that can surprise you with wonderful compilation errors.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

Maybe i'm wrong but chosen best anwser provided by Manu343726 has an error and won't compile. Both operator overloads have the same signature. Consider best anwser in question std::enable_if : parameter vs template parameter

P.S. i would put a comment, but not enough reputation, sorry

Community
  • 1
  • 1
The_Ham
  • 427
  • 3
  • 7
  • The first operator has `enable_if – CygnusX1 Nov 10 '14 at 12:56
  • @CygnusX1 Yes that's the case during template arguments substitution. But still, both signatures of operator overload are exactly the same, which to me means compilation error. If I paste this code as it is and compile under gcc 4.8 with -std=c++11 i get an error about it. You don't? – The_Ham Nov 11 '14 at 11:02
  • For some reason I get compiler errors too, because SFINAE was not triggered for some reason. Having same signature is OK and should not be a problem, as one of versions should get removed by the template mechanism. I updated the solution to a version that works on my compiler (clang++ 3.4, g++ 4.8.3) – CygnusX1 Nov 12 '14 at 17:26
  • Well i would think that it's expect behavior. Even if template won't be instantiated or indeed one of its default parameter won't be valid, we cant have two identical definitions. – The_Ham Nov 16 '14 at 18:00
  • SFINAE causes that one of the definitions does not exist. Have you checked the corrected version? – CygnusX1 Nov 16 '14 at 18:22
  • I know that corrected version compiles. What i'm saying is that you are wrong in thinking that "SFINAE causes that one of the definitions does not exist". The template with specific signature **does** exists and that's why earlier compiler was throwing an error about redefinition. SFINAE causes that invalid substitution of template parameters on one of the templates won't stop compiler from trying out other potentially suitable templates. – The_Ham Nov 18 '14 at 11:48