2

At work, I have come across code which basically looks like that:

#include <iostream>

using namespace std;

enum e_Specialization {
    Specialization_A,
    Specialization_B
};

template<e_Specialization>
class TemplatedBase {
public:
    string foo() { return "TemplatedBase::foo"; }
};

template<>
string TemplatedBase<Specialization_A>::foo() { return "TemplatedBase<Specialization_A>:foo"; }

int main() {
    TemplatedBase<Specialization_A> o;
    cout << o.foo() << endl;
    return 0;
}

which outputs

TemplatedBase<Specialization_A>:foo

I haven't been able to find any discussion on this technique anywhere.

The code's creator argued mostly from the optimization side of things, that no virtual dispatch happens. In our case this optimization is not necessary, but I see how it could be useful.

My questions are:

  1. Is this technique documented anywhere and does it have a name?

  2. In comparison to specialization by inheritance, are there any advantages to this at all?

    3. How does this relate to CRTP? To me it seems that the same is achieved, with all pros and cons of CRTP.

Community
  • 1
  • 1
schieferstapel
  • 1,025
  • 10
  • 15
  • I don't understand how this can achieve the same thing as CRTP. – cpplearner May 07 '17 at 14:07
  • advantages, compared to what? It's hard to say what advantages are and aren't in a vacuum. What alternative did you suggest? Also, your sole bit of implementation is very simple. Is it really important for foo to have a default implementation? How many functions are there? Etc. – Nir Friedman May 07 '17 at 14:10
  • @cpplearner: Both techniques achieve static polymorphism, i.e. remove the need for virtual – schieferstapel May 07 '17 at 14:34
  • @Nir Friedman: Yes, I forgot to mention: In comparison to specialization by inheritance. Updated question. – schieferstapel May 07 '17 at 14:37
  • 2
    "remove the need for virtual" CRTP doesn't remove the need for virtual dispatch. It allows you to conveniently express designs that don't need virtual dispatch in the first place. – n. m. could be an AI May 07 '17 at 14:59
  • @n.m.: One use of CRTP is exactly to get around virtual dispatch of specializations, see e.g. http://stackoverflow.com/questions/262254/crtp-to-avoid-dynamic-polymorphism . But after reading some more I agree that this technique doesn't do all that can be done with CRTP. – schieferstapel May 07 '17 at 15:11
  • 1
    @schieferstapel a misconception repeated many times doesn't become true. CRTP only allows to get around virtual dispatch in cases where it is not needed in the first place. Specifically, when types of objects are known at compile time. – n. m. could be an AI May 07 '17 at 15:30

1 Answers1

3

As far as whether the technique is documented (and if you are doing this with C++11 or later, please use an enum class), it's a fairly common technique to template on an enum or a boolean and then do your specializations.

One clear difference is that with this technique, you obviously can't add more specializations without modifying the primary code. An enum (or enum class) only has so many values. That could be either a good or bad thing, depending on whether you want it to be centrally tracked. But that could easily be changed by templating on a class and encapsulating it, a third option which isn't quite this technique nor does it involve public inheritance.

This technique has its biggest advantage, IMHO, in that you have the option to implement things inline. For example:

template<e_Specialization e>
class TemplatedBase {
public:
    void bar() {
        // code
        if (e == Specialization_A) {
        ...
        }
        // code
    }
};

I see this a lot with classes that are known from the outset to be in a performance critical path. There could be a boolean variable that controls whether or not intrusive performance profiling occurs. Because these branches are known at compile time, they are trivially optimized. This is a good way to do it because you can still use both versions of the class in the same build (e.g. run unit tests on both).

Another difference compared to inheritance, is that derived classes can easily add state if they need to. This technique as it stands would require specializing the whole class to add state. Again, this could be good or bad; a more constrained design is good if you don't need to break those constraints. And you can easily change the design to enable adding extra state:

template <e_Specialization e>
struct ExtraState {};

template <e_Specialization e>
class TemplatedBase : private ExtraState<e> {
...

A third, minor example, is that you aren't exposing any inheritance relationship. This is mostly small, but remember that you can get things like slicing or even implicit reference/pointer conversion. This is a pretty strict win for this technique.

In sum I would say that:

  • It's a win if you utilize the ability to implement something once and write the differences inline with no perf penalty
  • It's a win if you want to be explicit in your design about there being a limited number of implementations.

If neither of those are true then the design is a bit unorthodox and a little more complex compared to just using inheritance, although it doesn't really have any strong technical disadvantage. So if you have a good number of junior devs around, this code might be harder to read.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72