10

OK here we go. I'm trying to use the CRTP template in order to remove the need of polymorphism from my app. I use an aproach like the one bellow

template <RealType> class Base 
{

    void doSomething()
    {
         static_cast<RealType>(this)->doSomethingImpl()
    }

class Derived1 : public Base
{
    void doSomethingImpl()
    {
        /* do something, for real */
    }
}

class Derived2 : public Base
{
    void doSomethingImpl()
    {
        /* do something else */
    }
}

This aproach, if I understood correctly, allows my classes to have no vtable, so function calls are direct and don't require the extra indirection.

Now imagine I want to store all my Derived# classes in a container. Let's say a vector.

First approach : I can create a non-template SuperBase class from which Base inherits and store that in the container.

However it seems to me that if I want to do that, I will have to make doSomething virtual, in SuperBase. And my goal is mainly not to have a vtable.

Second approach : I use type erasure, i.e. something like boost::any to store my elements in the Vector. But then, I don't see a way I can iterate over the elements and call doSomething on them, because to "use" boost::any, I need to know the real type of the object when iterating.

Do you think what I want to do is even possible ?

Seems to me that it is because doSomething() is part of Base but except using inheritance, I can't see a way to do it ....

Dinaiz
  • 2,213
  • 4
  • 22
  • 32
  • 1
    To summarize one point from the two answers currently below, and probably some more later: you say, "my goal is mainly not to have a vtable". What? Normal goals are, "I want to write some code that works", or "I want to write some code that works and runs fast". vtables are an implementation detail, a way of providing dynamic polymorphism, and it sounds as though your *main* goal is to have dynamic polymorphism. – Steve Jessop Nov 30 '10 at 17:14
  • Thanks guys ! Well, the point of avoid vtables is indeed performance because this code is going to be used in low level functions (i.e. called very often) . In general I don't have a particular hate against vtables :) . The other thing is I was reading quite a bit about static polymorphism and I was very curious if it is just possible ! Apparently it's not. Thanks all for your answers ! – Dinaiz Nov 30 '10 at 17:40
  • 4
    Polymorphism is a tool that can be used when the exact type of some objects might depend on runtime events. If you need this, then polymorphism a very good tool. Abandoning it in favor of blunter tools in order to solve performance problems which aren't even proven to exist seems a truly dire mistake. If you're objects' types can only be determined at runtime, then you need runtime polymorphism. If you don't use the built-in one, you need to hack up your own. And that's _very_ hard to get right and be faster than the built-in one even for very special circumstances. – sbi Nov 30 '10 at 17:54
  • You may want to look at the answer in https://stackoverflow.com/questions/65628159/policy-class-design-but-without-making-the-whole-user-class-a-template/65634578#65634578 – puio Jan 09 '21 at 18:54

4 Answers4

12

And my goal is mainly not to have a vtable.

If you want this, then, unless you implement your own virtual dispatch system (which would likely be inferior to what the compiler does), you're stuck with templates, a.k.a. compile-time polymorphism. And as the name says, in order to use this, everything must be known at compile-time. If you need to make decisions based on runtime events (like, e.g., user input), you want runtime polymorphism.

I can't help but have to ask: Why do you want to avoid vtables? (And if you're so determined, why aren't you programming in C?)

sbi
  • 219,715
  • 46
  • 258
  • 445
8

You can do what you want with the proper theoretical operation which is NOT polymorphism, but unification. Most people have no idea what a sum type (discriminated union) is and what it is for which is why they consistently abuse inheritance, which is entirely unrelated.

Unions are more popular in C, for example X-Windows event messages are union based, because they're broken in C++.

A union is the correct way to represent heterogeneous data types as a single type, hence the name unification: it unifies all the components into a single type. Unions always have a finite known number of components, functions using unions invariably use a switch on the discriminant to select the right handler code.

OOP cannot provide unification: it provides subtyping.

Templates provide something quite different again: parametric polymorphism.

All three concepts are quite distinct in both theory and practice. Subtyping OOP style turns out to be the least useful because what it can represent is heavily restricted, however those restrictions do permit dynamic dispatch to an open set of subtypes, which is very nice if it applies to your problem.

So now it is clear, in your array, all you need to put is a union of all your classes, and your problem goes away.

Only .. the classes have to be POD types in C++ at present due to an unprincipled restriction. So the best solution is to use a union of raw C functions, since C function pointers are PODs.

Something like:

enum tag {tag_X, tag_Y};

struct X { int x; };
void px(X a) { cout << a.x; }
struct PX { tag t; X a; void (*p)(X); };

struct Y { double x; };
void py(Y a) { cout << a.x; };
struct PY {tag t; Y a; void (*p)(Y); };

union XY { PX anX; PY anY; };

PX x1 = { tag_X, { 1 }, px };
PY y1 = { tag_Y, { 1.0 }, py };

XY xy1.anPX = x1;
XY xy2.anPy = x2;

XY xys[2] = {xy1, xy1};

xy = xys[0];
switch (xy.anPX.tag) { // hack
  case tag_X: xy.anPX.p (xy.PX.x); break;
  case tag_Y: xy.anPY.p (xy.PY.x); break;
}

If you think this is ugly, you're right: C and C++ are brain dead. Another solution is to use a tag and a pointer which is cast to a void*, then use the tag to cast to the required type: this is much easier but requires heap allocation of the data and hence you have a memory management issue. Another alternative is Boost variant type (which automates some of the housekeeping but is still very ugly).

Here's similar code in Ocaml:

type xy = X of int | Y of double
let print u =
  match u with 
  | X x -> print_int x 
  | Y x -> print_double x
in 
  print (X 1);
  print (Y 2.0)

In this code the X and Y are the tags of the C code above, they're called type constructors because they construct an xy type out of an int or a double (resp.). The match expression there is just a switch with automatic selection of the right component type and scoping used to ensure you can't refer to the wrong component (as you could in the C code), also there's no break, match handlers don't drop thru, and the memory management is done by a garbage collector.

Yttrill
  • 4,725
  • 1
  • 20
  • 29
  • Note that in C++17, `std::variant` provides a strong enough sum type for tihs. `std::visit( var_element, [](auto&& e){ e.doSomething(); } )` then provides vtable-less dynamic dispatch, and lambdas get rid of much of the ugly. – Yakk - Adam Nevraumont Sep 05 '17 at 17:44
  • Ah, I did some work on that. Do you have a reference so I can see what was actually accepted? – Yttrill Sep 06 '17 at 23:35
  • @Yakk: thanks. Unfortunately variant is not what is really required, it is a type safe C union, which is not a proper sum type. The proposal I looked at was http://davidsankel.com/uncategorized/c-language-support-for-pattern-matching-and-variants/ which is a vastly superior but language intrinsic extension. The ISO variant <> uses a type as a discriminant instead of a tag, so that variants cannot support distinct variations of the same type which is required for a real variant type. Useless without pattern matching anyhow :) – Yttrill Sep 08 '17 at 01:46
  • No, `std::variant` supports `variant`. The tag used is the compile-time index. Calling `get` on such a variant is a compile-time error. Calling `get<0>`, `get<1>` or `get<2>` gets an `int`. Or you can use tagged types if you really need tags. – Yakk - Adam Nevraumont Sep 08 '17 at 18:51
  • Regardless, variant is strong enough to make most of the ugly C code above go away, and its pattern matching similar is sufficient to mimic the Ocaml code above in this case. – Yakk - Adam Nevraumont Sep 08 '17 at 18:56
  • @Yakk: Ah, ok thanks for clarification! Didn't realise that. Of course you cannot make the ugly code go away without pattern matching. Functional decomposition is horrific, pattern matches automate reversal of the extractor compositions. What I mean is that if you have a variant nested in another variant you first have to decode the outer variant, and then the inner one. With pattern matching, you can just write a linear sequence of patterns. Anyhow, at least you can do it. – Yttrill Sep 09 '17 at 03:25
4

You can combine both strengths.

#include <iostream>

struct AnimalBase
{
    virtual std::string speak() const = 0;
    virtual ~AnimalBase() {};
};

template <typename Derived>
struct Animal : AnimalBase
{
    std::string speak() const
    {
        return static_cast<const Derived*>(this)->speak();
    }
};

struct Dog : Animal<Dog>
{
    std::string speak() const
    {
        return "Woof! Woof!";
    }
};

struct Cat : Animal<Cat>
{
    std::string speak() const
    {
        return "Meow. Meow.";
    }
};

int main()
{
    AnimalBase* pets[] = { new Dog, new Cat };

    std::cout << Dog().speak() << '\n'
              << Cat().speak() << '\n'
              << pets[0]->speak() << '\n'
              << pets[1]->speak() << std::endl;

    delete pets[0];
    delete pets[1];
    return 0;
}
jdh8
  • 3,030
  • 1
  • 21
  • 18
  • 1
    Since this involves a virtual function in the base, does it preserve any of the benefit of using CRTP? It seems to defeat the point, to me. It looks liike you've merely moved the vtable indirection to a different location, while complicating the code. – underscore_d Sep 20 '15 at 15:50
  • 4
    @underscore_d There is vtable but only looked up when needed. `Dog().speak()` looks up no vtable but `pets[0]->speak()` does. – jdh8 Sep 27 '15 at 10:37
  • 2
    Thanks, your comment prompted me to do some more research. :) As long as we're using known types and a good compiler, such calls can benefit from devirtualisation. Some simple devirtualisations are even applied by `-O0`. Here's a good series on this: http://hubicka.blogspot.co.uk/2014/01/devirtualization-in-c-part-1.html – underscore_d Sep 27 '15 at 16:06
2

It's going to be difficult (a hack at best) to hold these objects in a container. You have designed away polymorphism, and yet you seem to really want to use it so you can hold the objects as container<mybaseclass> and use them polymorphically.

It's not clear to me from your posting why you wish to avoid the vtable. If this is for performance, you are probably over-optimizing. Without more background on why you are going this route, it's hard to recommend anything but 'use a base class'.

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140