Home > Blockchain >  C 03 Replace Preprocessor Directives with Template Metaprogramming
C 03 Replace Preprocessor Directives with Template Metaprogramming

Time:08-17

I have a embedded C 03 codebase that needs to support different vendors of gadgets, but only ever one at a time. Most of the functions overlap between the several gadgets, but there are a few exclusives, and these exclusive functions are creating a problem that I need to solve.

Here is an example of clumsy code that works using pre-processor conditionals:

#define HW_TYPE1    0
#define HW_TYPE2    1

#define HW_TYPE HW_TYPE1

struct  GadgetBase  {
    void    FncA();
    // Many common methods and functions
    void    FncZ();
};
#if HW_TYPE==HW_TYPE2
struct  Gadget  :   public  GadgetBase  {
    bool    Bar()       {return(true);}
};
#else
struct  Gadget  :   public  GadgetBase  {
    bool    Foo()       {return(false);}
};
#endif

Gadget  A;

#if HW_TYPE==HW_TYPE2
bool    Test()  {return(A.Bar());}
#else
bool    Test()  {return(A.Foo());}

Here is my attempt at converting the above code to C templates without pre-processor directives. The following code does not compile due to an error in the definition of Test() on my particular platform, because either Foo() or Bar() is undefined depending on the value of Type.

enum    TypeE   {
    eType1,
    eType2
};

const TypeE Type= eType1; // Set Global Type

// Common functions for both Gadgets
struct  GadgetBase  {
    void    FncA();
    // Many common methods and functions
    void    FncZ();
};

// Unique functions for each gadget
template<TypeE  E=  eType1>
struct  Gadget  :   public  GadgetBase          {
    bool    Foo()       {return(false);}
};
template<>
struct  Gadget<eType2>  :   public  GadgetBase  {
    bool    Bar()       {return(true);}
};

Gadget<Type>    A;
template<TypeE  E=  eType1>
bool    Test()  {return(A.Foo());}
template<>
bool    Test()  {return(A.Bar());}

I want to do this with templates to keep the number of code changes down when a new type or additional functions are added. There are currently five types with at least two more expected soon. The pre-processor implementation code reeks, I want to clean this up before it gets unwieldy.

The gadget code is a small amount of the total code base, so breaking up the entire project per gadget may not be ideal either.

Even though only one type will ever be used for each project, the unused types still have to compile, how do I best design this using C 03 (no constexpr, const if, etc)? Am I completely approaching this wrongly? I am willing to do a complete overhaul.

CodePudding user response:

I agree the code looks convoluted, I'm with you there. But I believe you are going in the wrong direction. Templates seem cool but they are not the right tool in this case. With templates you WILL always compile all the options every time, even if they are not used.

You want the opposite. You want to ONLY compile one source at a time. The proper way to have the best of both worlds is to separate each implementation in a different file and then pick which file to include/compile using external methods.

Build systems usually have plenty of tools for this respect. For example, for compiling natively, we can rely on CMAKE's own CMAKE_SYSTEM_PROCESSOR to identify which is the current processor.

If you want to cross compile you need to specify which platform you want to compile to.

Case in mind, I have a software that needs to be compiled in many operating systems like Redhat, CentOS, Ubuntu, Suse and Windows/Mingw. I have one bash script file that checks for the environment and loads a toolchain cmake file specific for that operating system.

Your case seems to be even simpler. You could just indicate which platform you'd like to use and instruct the build system to compile just the file specific to that platform.

CodePudding user response:

You are getting there :).

Rewrite your Test function so it doesn't rely on global Gadget object but instead takes one as a templated parameter:

template<class T>
bool Test(T &o, std::integral_constant<bool (T::*)(), &T::Foo> * = 0)
{
  return(o.Foo());
}

template<class T>
bool Test(T &o, std::integral_constant<bool (T::*)(), &T::Bar> * = 0)
{
  return(o.Bar());
}

And call it as:

Test(A);

This relies on SFINAE (Substitution Failure Is Not An Error) idiom. Based on the definitions compiler will deduce the type of T to be a Gadget. Now, depending on availability of the Foo and Bar function it will pick one of the overloads.

Please note this code WILL BREAK if both Foo and Bar are defined in Gadget as the two overloads will match.

This brings a question if you just can't wrap calls to Foo and Bar inside a Gadget class:

template<TypeE  E=  eType1>
struct  Gadget  :   public  GadgetBase          {
    bool    Foo()       {return(false);}
    bool    Test()      {return Foo();}
};
template<>
struct  Gadget<eType2>  :   public  GadgetBase  {
    bool    Bar()       {return(true);}
    bool    Test()      {return Bar();}
};

and consistently call A.Test() instead?

EDIT:

I might have over-complicated it. The following overload may be an easier approach to this:

bool Test(Gadget<eType1> &o)
{
  return(o.Foo());
}

bool Test(Gadget<eType2> &o)
{
  return(o.Bar());
}

Test(A);
  • Related