Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ - specialize class template's member function

I am looking for help with templates. I need to create function in template what will be reacting differently on a specific type.

It could looks like this:

template <typename T>
class SMTH
{
    void add() {...} // this will be used if specific function isn't implemented
    void add<int> {...} // and here is specific code for int
};

I also tried to use typeid and swich through types in a single function, but doesn't work for me.

like image 430
Jara M Avatar asked Mar 13 '23 18:03

Jara M


1 Answers

You really don't want to be doing this branching at runtime, with typeid.

We want this code:

int main()
{
    SMTH<int>().add();
    SMTH<char>().add();

    return 0;
}

To output:

int
not int

There are lot of ways I can think of to achieve this (all at compile time and half of them requires C++11):

  1. Specialize the whole class (if it has only this add function):

    template <typename T>
    struct SMTH
    {
        void add() { std::cout << "not int" << std::endl; }
    };
    
    template <>
    struct SMTH<int>
    {
        void add() { std::cout << "int" << std::endl; };
    };
    
  2. Specialize only the add member function (recommended by @Angelus):

    template <typename T>
    struct SMTH
    {
        void add() { std::cout << "not int" << std::endl; }
    };
    
    template <> // must be an explicit (full) specialization though
    void SMTH<int>::add() { std::cout << "int" << std::endl; }
    

Note that if you instantiate SMTH with cv-qualified int, you'll get the not int output for above approaches.

  1. Use the SFINAE idiom. There are few variants of it (default template argument, default function argument, function return type), and the last one is the one that fits here:

    template <typename T>
    struct SMTH
    {
        template <typename U = T>
        typename std::enable_if<!std::is_same<U, int>::value>::type // return type
        add() { std::cout << "not int" << std::endl; }
    
        template <typename U = T>
        typename std::enable_if<std::is_same<U, int>::value>::type
        add() { std::cout << "int" << std::endl; }
    };
    

    The main benefit is that you can make the enabling condition complex, e.g. using std::remove_cv to choose the same overload regardless of cv-qualifiers.

  2. Tag dispatching - chooses the add_impl overload based on if the instantiated tag inherits from A or B, in this case std::false_type or std::true_type. You still use template specialization or SFINAE, but this time it's done on a tag class:

    template <typename>
    struct is_int : std::false_type {};
    
    // template specialization again, you can use SFINAE, too!
    template <>
    struct is_int<int> : std::true_type {};
    
    template <typename T>
    struct SMTH
    {
        void add() { add_impl(is_int<T>()); }
    
    private:
        void add_impl(std::false_type)  { std::cout << "not int" << std::endl; }
    
        void add_impl(std::true_type)   { std::cout << "int" << std::endl; }
    };
    

    This can of course be done without defining the custom tag class, and the code in add will look like this:

    add_impl(std::is_same<T, int>());
    

I don't know if I mentioned them all, and I don't know why I attempted to, either. All you have to do now is to choose the one that fits the use the best.

Now, that I see, that you also wanted to check if a function exists. This is already long, and there's an existing QA about that.

like image 116
LogicStuff Avatar answered Mar 25 '23 05:03

LogicStuff