1

I have a following code:

#include <iostream>

class A
{};

class B
{};

template<typename T>
void Do(T data)
{
    std::cout << "Do() default\n";
}

template<>
void Do(A* data)
{
    std::cout << "Do(A*)\n";
}

template<>
void Do(B* data)
{
    std::cout << "Do(B*)\n";
}

int main(int argc, char* argv[])
{
    A* a = nullptr;
    B* b = nullptr;

    const A* aConst = nullptr;
    const B* bConst = nullptr;

    Do(a);
    Do(aConst);

    Do(b);
    Do(bConst);

    return 0;
}

which outputs:

Do(A*)
Do() default
Do(B*)
Do() default

How should I rewrite the code to share template specialization for const & non-const type without copy pasting the specialization with const keyword specifier so it produces output:

Do(A*)
Do(A*)
Do(B*)
Do(B*)
mezo
  • 423
  • 1
  • 7
  • 19
  • `template<>void Do(A* data)` calling `template<>void Do(const A* data)` ? Or do you want to avoid even the intermediate call? – Jeffrey Oct 30 '19 at 21:25
  • In case you go with overloads instead of specialization, a related and very common problem is code reuse for const/non-const _member_ functions. There are different solutions to that one, which can also be applied to const-qualified parameters in general. I proposed mine [here](https://stackoverflow.com/a/56694496/5427663]). – TheOperator Oct 30 '19 at 21:45

3 Answers3

3

Instead of specializing, you can overload. Using

template<typename T, std::enable_if_t<std::is_same_v<std::decay_t<T>, A>, bool> = true>
void Do(T* data)
{
    std::cout << "Do(A*)\n";
}

template<typename T, std::enable_if_t<std::is_same_v<std::decay_t<T>, B>, bool> = true>
void Do(T* data)
{
    std::cout << "Do(B*)\n";
}

These functions will be called when you pass const A*/A*/const B*/B* since they are a better match then the generic template. The reason they are a better match is because T is more constrained. It is considered more specialized and so it will win in a tie breaker with the generic template in overload resolution.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
0

You could use this pattern, if you are willing to write a small stub:

template<>
void Do(const A* data)
{
    std::cout << "Do(A*)\n";
}

template<>
void Do(A* data)
{
    Do((const A*)data);
}

The main code you don't want to duplicate uses const A*, because, well, you want it to work on const data too. The non-const one simply forward to it.

Jeffrey
  • 11,063
  • 1
  • 21
  • 42
0

C++14

I upvoted this answer which is a good showcase for SFINAE. To extend on it, you can simplify the SFINAE expression with std::is_convertible. This is more analogous how overload resolution would work (adding const qualifier).

template<typename T, std::enable_if_t<std::is_convertible_v<T*, const A*>, int> = 0>
void Do(T* data)
{
    std::cout << "Do(A*)\n";
}

template<typename T, std::enable_if_t<std::is_convertible_v<T*, const B*>, int> = 0>
void Do(T* data)
{
    std::cout << "Do(B*)\n";
}

C++17

As a bonus, in C++17 with constexpr if, you can use a single function for all cases:

template<typename T>
void Do(T data)
{
    if constexpr (std::is_convertible_v<T, const A*>)
        std::cout << "Do(A*)\n";
    else if constexpr (std::is_convertible_v<T, const B*>)
        std::cout << "Do(B*)\n";
    else    
        std::cout << "Do() default\n";
}
TheOperator
  • 5,936
  • 29
  • 42