3

I'm writing a C++ hardware abstraction layer (HAL), which needs to be as fast as possible.

Polymorphism offers the best API, but Virtual Table lookups really kill the speed of the code.

This lead me to using templates in conjunction with policies to get compile-time polymorphism. But because templates with different arguments get instantiated as completely different types, I can not use them interchangeably in function calls unless the function is a template as well.

However, I dont want to force the user of my HAL library to write all function as templates because I have used templates.

For illustration purposes, suppose this is my HAL:

template<typename T_POLICY>
class I2CManager {
public:     
    void send(uint8_t data) {
        T_POLICY::send(data);
        ++sent_data; 
    }
private:
    int sent_data; // Just to illustrate that I2CManager has state
};

class I2C1 {
    static void send(uint8_t data) { /* Run some code here */ }
};

class I2C2 {
    static void send(uint8_t data) { /* Run other code here */ }
};


// OTHER HW
template<typename T_POLICY>
class UARTManager { ··· };

class UART1 { ··· };
class UART2 { ··· };

template<typename T_POLICY>
class TIMERManager { ··· };

class TIMER1A { ··· };
class TIMER1B { ··· };

This works and I can now create a I2CManager with different policies, such as follows. I can even have several I2CManagers running with different policies at the same time.

I2CManager<I2C1> i2c1;
I2CManager<I2C2> i2c2;

i2c1.send(0x11); // This works
i2c2.send(0x11); // This also works

Now, i2c1 and i2c2 have the same public methods, yet they are not interchangeable. Consequently, the user of my HAL library is forced to use templates as well.

// THIS DOES NOT WORK
void foo(I2CManager m) { m.send(0x11); }
foo(my_manager_1);

// BUT THIS WORKS
template<typename T>
void foo(I2CManager<T> m) { m.send(0x11); }
foo(i2c1);

Can I somehow get compile-time polymorphism but allow the end-user to treat it as if it was normal polymorphism? I don't care if the inner code in my library gets ugly or difficult to read for the sake of speed, but the API has to be as simple and intuitive as possible.

Actually, I want foo() to be specialized (and replicated in code) for the different parameters as if it was a template, but I don't want the user of my library to notice it is a template function. Altought alternatives to templates are also welcome.

I don't know if this is even possible, but I have read something about concepts which will appear in the next C++ standard. I want compile-time polymorphism, but as userfrliendly as runtime polymorhism.


Considerations:

  • Because I'm interfacing HW, each instantiation of my HWManagers with different policies will be unique (i.e. There is only one HWManager instance, and one HWManager instance, and may or may not exist simultaneously).

  • All instances are created by the library as global variables, and are not heapable.

  • All policy methods are extremely short, so having multiple unique instances due to templates is preferable to Virtual Tables for the sake of execution speed.

  • Code size does not matter (its for embedded systems), but RAM usage and execution speed does. I need as much as possible to be solved during compile time. Again, I'm willing to have a over-bloated executable for the sake of avoiding run-time resolutions.

  • Only up to C++03 supported


Code example edited

andresgongora
  • 192
  • 12
  • 1
    not really. mixing compile-time and runtime polymorphism is usually not possible. You can try using `boost::variant<>` though as parameters for your template instanciated types. If you know all the types during compilation though – Hayt Sep 19 '16 at 10:17
  • http://stackoverflow.com/a/2354671/1870760 . Basically, you can't mix compile time and runtime polyphormism because there can be an unlimited amount of template instantiations of a specific method. You could provide some kind of default policy to use for the HWManager. – Hatted Rooster Sep 19 '16 at 10:18
  • @xaxxon The compiler doesn't know that. – Hatted Rooster Sep 19 '16 at 11:03
  • 1
    There are games you can play when you know the full set of types involved. You can create a common base type for all the templated types and then create your own, faked dynamic_cast type but one that's not based on a vtable, instead based on information you put in your objects (like an int). Then, the user can fake-dynamic-cast to the actual types involved and then call the function based on static dispatch. Both clang and the v8 javascript engine use this type of non-vtable rtti. There is no extra layer of indirection when calling the method this way. – xaxxon Sep 19 '16 at 11:07
  • One side comment, are you sure you need so much speed, that you have to remove the additional indirection that the vtables introduce? I ask this since the I2C bus is really really slow (max of 400 kHz in fast mode) compared to uC speed. Maybe you have a bottleneck somewhere else? Maybe is this a problem of premature optimization? – LoPiTaL Sep 19 '16 at 11:11
  • @LoPiTaL I do indeed, as I'm writing code for an embedded platform. For example, when accessing parallel port I get have a maximum access rate of 1200KHz. With templates and templated functions I do get those 1200KHz (as in the end everything gets inlined). But when using polymorphism I not only have indirection due to the vtable, but I loose inlining as well. In that case the access rate drops to 200KHz. – andresgongora Sep 19 '16 at 11:44
  • @xaxxon That sounds quite like what I am looking for. But I cant find anything on the net. Could you please post a reference? – andresgongora Sep 19 '16 at 11:47
  • @Hayt I have taken a look at boos::variant<>, but it still gets exposed to the end-user of the HAL library right? – andresgongora Sep 19 '16 at 11:50
  • @andresgongora yes it gets exposed but you can try "masking" them with typedefs. but when the template classes are part of your interface they get exposed. Or you can try to write wrapper classes around them. But this could result in a lot of duplication. – Hayt Sep 19 '16 at 11:52
  • I don't know of any. The general idea is that you assign a value to each class and have a reference of which values correspond to what other types and then the casting is just whether the two numbers have the appropriate mapping. I'll look a little more when I get home and not on mobile. The stuff I've seen is very macro heavy to automate most of the process of adding new types quickly. – xaxxon Sep 19 '16 at 18:47

1 Answers1

0

All instances are created by the library as global variables, and are not heapable.

You mean something like this, right?

static HWManager<DrierPolicy> drierManager;

static HWManager<FridgePolicy> fridgeManager;

Then, what wrong with letting the user know about them and allow her/him to use those directly like:

drierManager.doStuff();

fridgeManager.doStuff();

And then...

Because I'm interfacing HW, each instantiation of my HWManagers with different policies will be unique

Then why implement the doStuff method as instance ones? Aren't some static methods good enough?

(those are questions, not critiques. Yes, I know, this is hardly an answer - it may be one though - but I needed the extra formatting that the comments do not provide)

Adrian Colomitchi
  • 3,884
  • 1
  • 12
  • 23
  • All policies are jut a bundled bunch of static methods indeed. But the Managers have have private variable (and consequently state). Nevertheless, each instantiation of the manager is unique because, even if I have two fridges, they will have different policies (due to different configuration vectors). Also, I have different HWManagers depeding on the specific HW type they manage (I will update this in the question). For example, I have I2CManager, UARTManager and TimerManager. Then, they can be specialized with policies for the actual HW (i2c1, i2c2, uart1, uart2, timer1A, timer1B...) – andresgongora Sep 19 '16 at 10:33
  • @andresgongora Is it wrong having the user know about the private variables - the calling code won't be able to access them anyway? If there is a compelling reason reason to 'obscure' them, would [PIMPL](http://stackoverflow.com/questions/60570/why-should-the-pimpl-idiom-be-used) do? – Adrian Colomitchi Sep 19 '16 at 10:56
  • @andresgongora The way you describe, it looks like the 'manager' is just a placeholder for maintaining a state, all the operations being actually delegated to the 'policies` classes, which are `just a bunch of static methods`. If it is _exclusively_ so, can you invert the design and see the 'policy methods' as the main functions which operate using a `manager*` as a parameter? Like the good-old C, in which you call 'insert_node(list*, int value)' rather than `list.insertNode(int value)` – Adrian Colomitchi Sep 19 '16 at 11:05
  • I had a look at PIMPL (ty for the link), but it adds pointers, which I try to avoid when using very thing control loops as in this case (I want HW access to be as fast as possible willing to increase compiled code size). Regarding the other option you mentioned... let my think about it. But it also seems to add pointers. But you are right, it could be turned around and let I2C accept a manager as parameter. Yet this would still force the user to write all functions that use I2C as templates – andresgongora Sep 19 '16 at 11:29
  • @andresgongora - nothing wrong with pointers performance-wise. You can still use a pointer to a compile-time-know, statically allocated manager which will never be deleted. – Adrian Colomitchi Sep 19 '16 at 11:54
  • No. Obscuring the details of the implementation is not that important. For example, I would like to hand the end user two simple I2C instances compiled for its specific HW. I dont care if he sees what specific classes is used (as in normal polymorphism), but I dont want to force him to use the temaplate idiom in case he wants to write a function that accepts either I2C1 or I2C2. Note that I fully understand that such a function would be compiled twice for each I2C (as a template). – andresgongora Sep 19 '16 at 11:56
  • @andresgongora - I2C functions as templates and reluctance to do it... Mmmm... You can 'de-templetise' them youself and declare bunches of `I2C1_dostuffX/Y/Z(HWManager* state, void*data)` then `I2C2_dostuff(HWManager* state, void*data)`. Or, if the operations are absolutely the same, you can use an extra parameter `enum I2C_protocol_ver` to `I2C_dostuffX/Y/Z(I2C_protocol_ver, HWManager* state, void* data)` and 'dispatch' those calls to the appropriate 'specialized and instantiated policy' - those functions would be a pretty thin shim easy to maintain. – Adrian Colomitchi Sep 19 '16 at 12:06
  • @andresgongora PIMPL is good not only to obscure the things for the user, but also for the compiler - i.e. dealing with a pointer only makes the calling code absolutely immune to changes in the structure of your 'PIMPL-ed structures' – Adrian Colomitchi Sep 19 '16 at 12:12
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/123720/discussion-between-andresgongora-and-adrian-colomitchi). – andresgongora Sep 19 '16 at 16:07