Search code examples
c++interfacecallbackembeddedvariadic-templates

Variadic template callback interface class instanciation cannot be overloaded


What I'm trying to do is to have a callback interface with function members only. I have, today, 2 final classes, that use this interface to define their behavior by saving specific callbacks in the CallbackStore.

What I achieved, from my constraints (here), is this following minimal compiling code, but only with one child class:

#include <iostream>
#include <stdint.h>

using namespace std;

static constexpr int STORE_SIZE = 4;

void* operator new(size_t size)
{
    cout << "ERROR HEAP USED" << endl;
}

template<typename T, size_t storeSize>
class CallbackStore
{
public:

    CallbackStore() : that_(nullptr) {};
    CallbackStore(T* that) : that_(that) {};

    using CallbackCondition = bool (*) (T*);
    using CallbackAction = void (*) (T*,int);
    struct Step
    {
        CallbackCondition pCallbackCondition;
        CallbackAction pCallbackAction;
    };
    void setStep(int stepId,CallbackCondition pCallbackCondition, CallbackAction pCallbackAction)
    {
        if(stepId<storeSize)
        {
            store[stepId].pCallbackCondition = pCallbackCondition; 
            store[stepId].pCallbackAction = pCallbackAction; 
        }
        else
        {
            cout << "pointer error" << endl;
        }
    }
    void callStep(int stepId, int param) 
    {
        if((stepId<storeSize) &&
        (store[stepId].pCallbackCondition != nullptr) &&
        (store[stepId].pCallbackAction != nullptr) &&
        (that_ != nullptr))
        {
            bool isActive =  (*(store[stepId].pCallbackCondition))(that_);
            if(isActive) {(*(store[stepId].pCallbackAction))(that_,param);}
        }
        else
        {
            cout << "pointer error" << endl;
        }

    }
    Step store[storeSize];
    T* that_;
};

template<typename Base, typename... ArgT>
class Interface : public Base // interface
{
public:
    Interface() : Base() {};
    Interface(ArgT... arg) : Base(arg...) {};

public:
    static bool True(Base* baseInstance)
    {
        return true;
    }
    static bool IsNegative(Base* baseInstance)
    {
        return ((static_cast<Base*>(baseInstance))->getValue() < 0);
    }
    static bool IsNull(Base* baseInstance)
    {
        return ((static_cast<Base*>(baseInstance))->getValue() == 0);
    }
    static bool IsPositive(Base* baseInstance)
    {
        return (IsNegative(baseInstance) == false);
    }
    static void PrintValue(Base* baseInstance, int value)
    {
        cout << "print this value : " << value << "." << endl;
    }
};


template<typename Base>
class Interface<Base,void>// interface
{
public:
    Interface() : Interface<Base,void>() {};
};

class MotherA
{
public:
    MotherA(){};
    MotherA(int x): x_(x){};
    int getValue() { return x_; }
    void setValue(int x) { x_ = x; }

protected:
    int x_ = -3; 
};

class ListModel : public MotherA
{
};
class FinalLChild : public Interface<ListModel>, public CallbackStore<ListModel, STORE_SIZE>
{
public:
    FinalLChild(): Interface(), CallbackStore(this)
    {
        setStep(0, &Interface::IsNegative,  &Interface::PrintValue ); 
        setStep(1, &Interface::IsNegative,  &Interface::PrintValue ); 
        setStep(2, &Interface::IsNull,      &Interface::PrintValue ); 
        setStep(3, &Interface::True,        &Interface::PrintValue ); 
    };

};

class ValueModel : public MotherA
{
public:
    ValueModel() : MotherA(), y_(0) {};
    ValueModel(int x,int y) : MotherA(x), y_(y) {};
    void reset(){x_= y_;};
    int y_ = 0;
};

class ValueChild : public Interface<ValueModel,int,int>, public CallbackStore<ValueModel, STORE_SIZE>
{
public:
    ValueChild() : Interface(), CallbackStore(nullptr){};
    ValueChild(int x,int y): Interface(x,y), CallbackStore(this){};
};

class FinalVChild : public ValueChild
{
public:
    FinalVChild():ValueChild(2,0)
    {
        setStep(0, &Interface::IsPositive,  &Interface::PrintValue ); 
        setStep(1, &Interface::IsPositive,  &Interface::PrintValue ); 
        setStep(2, &Interface::IsNull,      &Interface::PrintValue ); 
        setStep(3, &Interface::IsNull,      &Interface::PrintValue ); 
    };
};


int main()
{
    FinalVChild c;
    for(int i = 0; i < STORE_SIZE; i++)
    {
        c.callStep(i,8);
    }
    cout << "reset:\n";
    c.reset();
    for(int i = 0; i < STORE_SIZE; i++)
    {
        c.callStep(i,8);
    }
    // shall print "print this value : 8." 3 times if x_ is null, twice if x_ is negative.
}

By adding a new child class,

class ListModel : public MotherA
{
};

class FinalLChild : public Interface<ListModel>, public CallbackStore<ListModel, STORE_SIZE>
{
public:
    FinalLChild(): Interface(), CallbackStore(this)
    {
        setStep(0, &Interface::IsNegative,  &Interface::PrintValue ); 
        setStep(1, &Interface::IsNegative,  &Interface::PrintValue ); 
        setStep(2, &Interface::IsNull,      &Interface::PrintValue ); 
        setStep(3, &Interface::True,        &Interface::PrintValue ); 
    };

};

and in the main()

    FinalLChild d;
    cout << "d:\n";
        for(int i = 0; i < STORE_SIZE; i++)
    {
        d.callStep(i,8);
    }

this throws the following compilation error:

main.cpp: In instantiation of ‘class Interface<ListModel>’:
<span class="error_line" onclick="ide.gotoLine('main.cpp',113)">main.cpp:113:57</span>:   required from here
main.cpp:65:5: error: ‘Interface::Interface(ArgT ...) [with Base = ListModel; ArgT = {}]’ cannot be overloaded
     Interface(ArgT... arg) : Base(arg...) {};
     ^~~~~~~~~
main.cpp:64:5: error: with ‘Interface::Interface() [with Base = ListModel; ArgT = {}]’
     Interface() : Base() {};
     ^~~~~~~~~

Solution

  • I suppose the problem is this couple of constructors

    Interface() : Base() {};
    Interface(ArgT... arg) : Base(arg...) {};
    

    When ArgT... is the empty list, they are the same constructor and collide.

    I propose to SFINAE enable/disable the second one as follows

    template <bool B = (sizeof...(ArgT)>0u), std::enable_if_t<B, bool> = true>
    Interface(ArgT... arg) : Base(arg...) {};
    

    This way the second construct is enabled only when ArgsT... isn't empty.

    An alternative could be avoid SFINAE (the second constructor ever enabled) but transform it in a template one, with and variadic un-named list of unused template parameters, so the first one has the precedence

    Interface() : Base() {};
    
    template <typename ...>
    Interface(ArgT... arg) : Base(arg...) {};