Joseph Franciscus
Joseph Franciscus

Reputation: 371

Compiling two different implementations of the same class

Currently I am writing a class that supports data proccessing on the cpu or gpu utilizing preprocessor definitions to determine which header file to include.

IE

#ifdef CPU_work
#include "cpu_backend.h"
#endif

#ifdef GPU_work
#include "gpu_backend.h"
#endif

class Work {
//Implementation dependant upon included header 
}

However, there maybe instances where I would need both variants. Is there anyway I could do something like....

namespace CPU {
    #define CPU_work
    //Generate implementation of WorkClass with cpu_backend.h
}
namespace GPU {
      #define GPU_work
      //Generate implementation of WorkClass with gpu_backend.h
}

and therefor determine which implementation I want via something like...

CPU::Work cpuObject;
GPU::Work gpuObject;

Would be happy with any work-arounds also. Much thanks JJ.

Upvotes: 1

Views: 1388

Answers (2)

WindyFields
WindyFields

Reputation: 2875

Interesting question:) If I understood your goals correct, I can suggest a few solutions.

First uses template specialization, template default arguments and (of course) some macros.

Check this out:

// cpu_backend.h 
#define CPU_BACKEND

class UseCPU;

#ifndef GPU_BACKEND
template<class Method = UseCPU>
struct Backend;
#endif

template<>
struct Backend<UseCPU>
{
    char* Info() { return "CPU"; }
};

// gpu_backend.h
#define GPU_BACKEND

class UseGPU;

#ifndef CPU_BACKEND 
template<class Method = UseGPU>
struct Backend;
#endif 

template<>
struct Backend<UseGPU>
{
    char* Info() { return "GPU"; }
};

// main.cpp
// Try to swap comments on headers 
// and see how output changes

#include "cpu_backend.h"
//#include "gpu_backend.h"

#include <iostream>

template<class ... Method>
struct Work
{
    Work()
    {
        std::cout << "I use " << backend.Info() << std::endl;
    }

private:
    Backend<Method ...> backend;
};

int main()
{
    Work<> work;
    // Uncomment these two while including both headers
    //Work<UseCPU> cpuWork; 
    //Work<UseGPU> gpuWork;
    return 0;
}

If you use MSVC you can simplify example above eliminating #define and #ifndef.

Trick: MSVC (2017 and maybe earlier versions) allow to omit that macros thresh, just ignoring the second declaration if they meet in the same compilation unit, like this:

template<class Method = UseCPU>
struct Backend;
template<class Method = UseGPU>
struct Backend;

BUT this will be not standard. Standard does not allow specifying default template args twice.

Meanwhile, this solution has few drawback:

  • When you include both headers, someone still can say Work<> which will use the backend specified by the first header you included. However, it would be better if compiler forced a person to specify a backend type explicitly in this circumstances, because otherwise it relies on the header inclusion order which is bad (say hello to macros).

  • Also, it assumes that both backends have the same API (like Info() in my case)

Possible Fixes for those:

  • I am sure it is possible to make compiler give an error when both headers are included and no explicit backend was specified, but it probably involves more preprocessor things or some SFINAE...

  • If your backends do have different APIs, then you can insert a few #ifdef where needed or (preferably) use C++17 if constexpr(std::is_same<Method, UseCPU>()::value) if you have access to such cool features:)

Upvotes: 0

jakedipity
jakedipity

Reputation: 900

This might be the place to use a template method design. Your base class implements everything that is common to both CPU and GPU and then you use abstract functions where there are differences.

class Work {
public:
    void execute() {
        // Do some initializing
        foo();
        // Do some middle stuff
        bar();
        // Do some final stuff
    }

private:
    virtual void foo() = 0;
    virtual void bar() = 0;
}

class CpuWork: public Work {
    virtual void foo() {
        // Do some CPU stuff
    }
    virtual void bar() {
        // Do some more CPU stuff
    }
}

class GpuWork: public Work {
    virtual void foo() {
        // Do some GPU stuff
    }
    virtual void bar() {
        // Do some more GPU stuff
    }
}

You now can't use your base class Work by accident since it's abstract and you can't accidentally invoke your derived classes foo or bar since they are private members of the base class.

Upvotes: 3

Related Questions