3

This question might fall into "wanting the best of all worlds" but it is a real design problem that needs at least a better solution.

Structure needed: enter image description here

In order of importance, here's the requirements that have me stuck

  • We need templates, whether on the class or function level. We are highly dependent on template objects in arguments of functions at this point. So if anything leaves the model below, its virtual functions (to my knowledge).
  • We want to decouple the call from selection. By that we want the user to declare a Math Object and have the background figure it out, preferably at runtime.
  • We want there to be a default, like shown in the above diagram.

In my company's program, we have a crucial algorithm generator that is dependent on both compile-time and runtime polymorphism, namely template classes and virtual inheritance. We have it working, but it is fragile, hard to read and develop and has certain features that won't work on higher optimization levels (meaning we are relying on undefined behavior somewhere). A brief outline of the code is as follows.

// Math.hpp
#include <dataTypes.hpp>

// Base class. Actually handles CPU Version of execution
template <typename T>
class Math {
    // ...

    // Example function. Parameters vary in type and number
    // Variable names commented out to avoid compile warnings
    virtual void exFunc ( DataType<T> /*d*/, float /*f*/ )
    {
        ERROR_NEED_CODE; // Macro defined to throw error with message
    }

    // 50+ other functions...
};

//============================================================
// exampleFuncs.cpp
#include<Math.hpp>

template <> void Math<float>::exFunc ( DataType<float> d, float f)
{
    // Code Here.
}

Already, we can see some problems, and we haven't gotten to the main issue. Due to the sheer number of functions in this class, we don't want to define all in the header file. Template functionality is lost as a result. Second, with the virtual functions with the template class, we need to define each function in the class anyways, but we just shoot an error and return garbage (if return needed).

//============================================================
// GpuMath.hpp
#include <Math.hpp>

// Derived class. Using CUDA to resolve same math issues
GpuMath_F : Math<float> { ... };

The functionality here is relatively simple, but I noticed that again, we give up template features. I'm not sure it needs to be that way, but the previous developers felt constrained to declare a new class for each needed type (3 currently. Times that by 50 or so functions, and we have severe level of overhead).

Finally, When functionality is needed. We use a Factory to create the right template type object and stores it in a Math pointer.

// Some other class, normally template
template <typename T>
class OtherObject {
    Math<T>* math_;

    OtherObject() {
         math_ = Factory::get().template createMath<T> ();
         // ...
    }
    // ...
};

The factory is omitted here. It gets messy and doesn't help us much. The point is that we store all versions of Math Objects in the base class.

Can you point me in the right direction for other techniques that are alternative to inheritance? Am I looking for a variation of Policy Design? Is There a template trick?

Thanks for reading and thanks in advance for your input.

Sergey
  • 7,985
  • 4
  • 48
  • 80
Clint Chelak
  • 232
  • 2
  • 9
  • 2
    If understand it correctly, you basically want some sort of run-time overload resolution. Yet you are asking for “template tricks” which is inherently compile-time. Are you sure you are asking the right question? – Henri Menke May 24 '18 at 04:43
  • 1
    I'll be honest, no idea what the question is. I do see some egregious abuse of OOP though – Passer By May 24 '18 at 04:56
  • *"ERROR_NEED_CODE;"*, Do you need pure virtual function instead ? Then `template class Math` would "simply" be an interface. – Jarod42 May 24 '18 at 10:31
  • @HenriMenke Yeah, asking "template tricks" is not the right question because like you said, it is a run-time overload issue. I got caught down a rabbit hole that referenced "solutions" to run-time/compile-time coexistence was to pile up some templates, essentially removing inheritance. – Clint Chelak May 24 '18 at 20:09
  • @PasserBy I agree about the OOP abuse. It feels held together by brittle rubber bands. To clarify the question: how would you implement design illustrated in image? How would you select runtime choice of template class/functions that come in different forms themselves? – Clint Chelak May 24 '18 at 20:14
  • @Jarod42 I would agree; however, how do you make a pure virtual function with templated arguments in a function? – Clint Chelak May 26 '18 at 20:31
  • Your method is not template. It is your class which is template. – Jarod42 May 27 '18 at 15:37
  • @Jarod42 However, as you can see above, parameters passed in are using the template. – Clint Chelak May 29 '18 at 17:38
  • similar question and structure demanded: https://stackoverflow.com/q/6703199/6890010 – Clint Chelak Jun 05 '18 at 04:28

1 Answers1

1

As has been discussed many times before, templates with virtual features don't jive well together. It is best to choose one or the other.

Approach 1 : Helper Class

The first and best option we have so far does just that, opting out of the virtual features for a wrapper class.

class MathHelper {
    Math cpuMath;
    GpuMath gpuMath;
    bool cuda_; //True if gpuMath is wanted

    template <typename T>
    void exFunc ( DataType<T> d, float f )
    {
        if (cuda_)
            gpuMath.exFunc( d, f );
        else
            cpuMath.exFunc( d, f );
    }
    // 50+ functions...
};

First, you might have noticed that the functions are templated rather than the class. It structurally is more convenient.

Pros

  • Gains full access to templates in both CPU and GPU classes.
  • Improved customization for each and every function. Choice of what is default.
  • Non-invasive changes to previous structure. For example, if this MathHelper was just called Math and we had CpuMath and GpuMath as the implementation, the instantiation and use can almost be the same as above, and stay exactly the same if we let Factory handle the MathHelper.

Cons

  • Explicit if/else and declaration of every function.
  • Mandatory definition of every function in MathHelper AND at least one of the other Math objects.
  • As a result, repeated code everywhere.

Approach 2: Macro

This one attempts to reduce the repeated code above. Somewhere, we have a Math function.

class Math {
    CpuMath cpuMath;
    GpuMath gpuMath;
    // Some sort of constructor
    static Math& math() { /*static getter*/ }
};

This math helper uses a static getter function similar to Exam 1 shown here. We have base class CpuMath that contains no virtual functions and derived class GpuMath. Again, templating is on function level.

Then from there, any time we want a math function we use this macro:

#define MATH (func, ret...) \
do { \
    if (math.cuda_) \
        ret __VA_OPT__(=) math().cuda.func; \
    else \
        ret __VA_OPT__(=) math().cpu.func; \
} while (0)

Pros

  • Remove repeat code of previous wrapper.
  • Again, full power of templates unlocked

Cons

  • Not as customizable as above wrapper
  • Initially much more invasive. Every time a Math function is accessed, it has to change from val = math_.myFunc(...), to MATH (myFunc(...), val). Because editors don't do good error checking on macros, this has potentially to cause many errors in the editing process.
  • Base class must have every function derived class have, since it is default.

Again, if any other creative ways around to implement this design would be appreciated. I found this to be a fun exercise either way, and would love to continue learning from it.

Clint Chelak
  • 232
  • 2
  • 9