Reputation: 1380
I am trying to figure out the advantages and disadvantages of the CRTP idiom as opposed to template specialization. Let's say I want to create a "Renderer" class, and the renderer will either be implemented using "Vulkan" or "OpenGL".
With CRTP:
template <typename T>
class Renderer
{
public:
void draw(Shape s) { static_cast<T*>(this)->drawImpl(s); };
};
class VulkanRenderer : public Renderer<VulkanRenderer>
{
public:
void drawImpl(Shape s) { /* Vulkan implementation */};
};
class OpenGLRenderer : public Renderer<OpenGLRenderer>
{
public:
void drawImpl(Shape s) { /* OpenGL implementation */ };
};
With template specialization and class tags:
class Vulkan;
class OpenGL;
template<typename T>
class Renderer
{
public:
Renderer() { assert(false, "Type not support"); }
void draw(Shape s);
};
template<>
class Renderer<Vulkan>
{
public:
void draw(Shape s) { /* Vulkan implementation */ };
};
template<>
class Renderer<OpenGL>
{
public:
void draw(Shape s) { /* OpenGL implementation */ };
};
Are there certain advantages of the one method over the other? Are there certain things I can do with the one that I won't be able to do with the other? Are there any alternatives for static polymorphism techniques?
Also, is there any advantage to either of these methods of simply defining separate classes such as "VulkanRenderer" and "OpenGLRenderer". Also, would concepts perhaps not be a better way that both of these?
Upvotes: 4
Views: 633
Reputation: 106196
With CRTP, you can put any shared code/types/constants etc. into the base template, supporting both implementations. If you don't have things to share, there's no point using CRTP. It does have the usually easily managed downside that dynamically created derived objects risk accidental or careless deletion via a base class pointer, causing undefined behaviour.
With the class tags, the only difference from having two unrelated classes is that it's easier to constrain other templates to require some kind of Renderer (i.e. template <typename ARenderer> void f(Renderer<ARenderer>& r);
), rather than having a requirement on unrelated classes like std::is_same_v<T, OpenGLRenderer> || std::is_same_v<T, VulcanRenderer>
- not much utility. If the renderers really have nothing in common except the semantics of their interface, then this is an option.
Upvotes: 2