-1

My application requires end-users to write some classes to which I'll feed input data and take output data from. These user classes can be subclasses of a base class I provide. A way that's worked since the 90s would be a template allowing members to be declared as follows.

The initializer of the user class would pass in the object pointer to the constructor of these bindings. (Let's assume BaseClass has some vectors of pointers to such objects and the template adds the binding thingies to those vectors.)

class UserClass: public BaseClass {
    UserClass() :
        bi1( this ), bi2( this ), bi3( this ), bd1( this ), bd2( this )
    {
    }
    BoundInput<int>     bi1, bi2, bi3;
    BoundOutput<double> bd1, bd2;
}

Later the base class would be able to populate bi1, bi2, bi3 with the initial data, and call the user function.

Then user code that receives these variables would have to write:

virtual void Process() {
    int i1 = bi1.get(), i2 = bi2.get(), i3 = bi3.get();
    double d1, d2;

    // calculate d1 and d2 from i1, i2, i3

    bd1.set( d1 );
    bd2.set( d2 );
}

So the question is: how to avoid the end-user having to 1) use a special template like BoundInput<> etc., and instead just use the types they want; 2) pass this into constructors, 3) avoid having to explicitly declare "regular" variables and explicitly get and set values.

My dream is that the end user can simply write the following:

class UserClass: public BaseClass {
    UserClass()
    {
        BindInputs( i1, i2, i3 );
        BindOutputs( d1, d2 );
    }
    int     i1, i2, i3;
    double  d1, d2;

    virtual void Process() {
        // calculate d1 and d2 from i1, i2, i3
    }
}

Then the framework could directly populate i1, i2, i3 before calling Process(), and afterwards extract values from d1 and d2.

I feel BindInputs() and BindOutputs() could be a function templates? with a class... somehow... that uses class template argument deduction (CTAD) to figure out the first template?

(I won't know a priori what the inputs and outputs will be, so I don't think I can simply supply inputs as method parameters or have the function return an little struct of outputs. But if there's a way to do that that'd acceptable.)

Is there no way to make it just that simple, at least from the end-user's standpoint?

Bonus points if BindInputs() and BindOutputs() could easily be written WITHOUT the user class being a subclass of a specific base class... I can think of ways that could be fudged in single-threaded calling of initializers, using a global variable used by the BindInputs() and Outputs() functions, maintaining vectors of inputs and outputs in an object that points to the user object. Most methods would be fine, including variable argument macros and the like. I'd strongly prefer not using one that involved linear searches, hashes and so on, though.

BTW, I expect there would need to be per-template types in the background that can for instance take input from and send output to whatever the bound format is: a text stream, byte stream, SQL, whatever. I'd assume that end-users would have to write such classes for their own special types, and I'm fine with that being a little complicated but it has to be at least doable.

Swiss Frank
  • 1,985
  • 15
  • 33
  • 4
    (Not the downvoter). I think what you're asking is probably possible, although I'm having trouble really understanding what you are asking for. If you were able to provide a [mcve] of what works right now, and more clarity on where you're trying to go, that'd be really helpful. I'm having trouble understanding exactly what `BoundInput`, `BoundOutput` and `BaseClass` are, and what the user is doing with them. – AndyG Oct 06 '22 at 12:51
  • 2
    Would CRTP work for you? – lorro Oct 06 '22 at 12:58
  • looking at CRTP, lorro, thx – Swiss Frank Oct 06 '22 at 13:08

1 Answers1

1

Consider:

#include <tuple>

template<typename IN, typename OUT>
struct BaseClass {
    IN _in;
    OUT _out;
    template<std::size_t I>
    typename std::tuple_element<I, IN>::type&
    in() {
        return std::get<I>(_in);
    }
    template<std::size_t I>
    typename std::tuple_element<I, OUT>::type&
    out() {
        return std::get<I>(_out);
    }
};

struct UserClass : public BaseClass<
    std::tuple<int, int, int>,
    std::tuple<double, double>
> {
    virtual void Process() {
        int     i1 = in<0>(), i2 = in<1>(), i3 = in<2>();
        double  &d1 = out<0>(), &d2 = out<1>();
    }
};
KamilCuk
  • 120,984
  • 8
  • 59
  • 111