Home > OS >  Can I hide implementation details of this concept from the end user?
Can I hide implementation details of this concept from the end user?

Time:07-05

I have looked at several similar questions on SO. Maybe I am not grokking the solutions there. In those questions when the return type is auto or templated then separating declaration and definition in two different units causes a failure in compilation. This can be solved by explicitly declaring a concrete signature for the function definition. In my case I am not sure how to do that.

My scenario is as below:

// api.h
template <typename TImpl>
concept IsAProcessor = requires(TImpl impl)
{
    impl.init();
    impl.process();
    impl.deinit();
};


enum UseCase {
    USECASE1,
    USECASE2
};

template <IsAProcessor TImpl>
void Process(TImpl& impl)
{
    impl.process();
}

class Engine
{
public:
    IsAProcessor auto getInstance(UseCase a);
};
// End - api.h

// api.cpp
#include "api.h"
#include "third_party.h"
IsAProcessor auto Engine::getInstance(UseCase a) {
    switch (UseCase) {
        case USECASE1:
            return UseCase1Impl(); // Defined in third_party.h and satisfies concept requirement.
        case USECASE2:
            return UseCase2Impl();
    }

    
}
// End - api.cpp

// third_party.h
class UseCase1Impl {
public:
    void init(void);
    void process(void);
    void deinit(void);
}
// End - third_party.h

// third_party.cpp
#include "third_party.h"
void UseCase1Impl::init(void) {...};
// and so forth
// End - third_party.cpp

// User code
#include "api.h"

{
    auto en = Engine();
    auto usecase = en.getInstance(UseCase::USECASE1);
    //^^^ cannot be used before it is defined here    
    Process(usecase);
}

As I mentioned in the question, it is not desirable to expose UseCase1Impl and UseCase2Impl. How do I get past the error: function 'getInstance' with deduced return type cannot be used before it is defined

CodePudding user response:

The return type of a function is a static property, it can't change based on runtime data.

If you can, lift UseCase to a template parameter, and use if constexpr to have exactly one active return for each instantiation.

template<UseCase a>
auto Engine::getInstance() {
    if constexpr (a == USECASE1)
        return UseCase1Impl(); // Defined in third_party.h and satisfies concept requirement.
    if constexpr (a == USECASE2)
        return UseCase2Impl();
}

If you can't do that, you will have to find a common type to return.

struct IProcessor
{
    virtual ~IProcessor() = default;
    virtual void init() = 0;
    virtual void process() = 0;
    virtual void deinit() = 0;
};

template <IsAProcessor T>
class ProcessorFacade : public IProcessor
{
    T impl;
public:
    template <typename... Args>
    ProcessorFacade(Args&&... args) : impl(std::forward<Args>(args)...) {}

    void init() final { impl.init(); }
    void process() final { impl.process(); }
    void deinit() final { impl.deinit(); }
};

std::unique_ptr<IProcessor> Engine::getInstance(UseCase a) {
    switch (UseCase) {
        case USECASE1:
            return std::make_unique<ProcessorFacade<UseCase1Impl>>();
        case USECASE2:
            return std::make_unique<ProcessorFacade<UseCase2Impl>>();
    }
}
  • Related