Search code examples
c++c++11reflectionruntimelazy-initialization

How do I (elegantly) assign reference members according to a char alias?


Disclaimer

I don't actually propose to apply this design anywhere, but I've been curious nonetheless how one would implement this in C++, in particular given C++'s lack of reflection. (I'm simultaneously learning and experimenting with C++11 features, so please do use C++11 features where helpful.)

The Effect

What I want to achieve is almost purely cosmetic.

I'd like a class that binds itself to an arbitrary number of, say, vectors, using referenced members (which, as I understand, must be initialized during construction), but provides "aliases" for accessing these vectors as members.

To give a minimal example, I want this to work—

std::vector<int> one;
std::vector<int> two;
std::vector<int> three;

Foo foo(std::make_pair('B', one),
        std::make_pair('D', two),
        std::make_pair('F', three));

foo.DoSomething();

where

class Foo
{
public:
    // I'm using variadic templates here for sake of learning,
    // but initializer lists would work just as well.
    template <typename ...Tail>
    Foo(std::pair<char, std::vector<int>&> head, Tail&&... tail) // : ???
    {
        // ???
    }

    virtual void DoSomething()
    {
        D.push_back(42);
        std::cout << D[0] << std::endl;
    }
private:
    std::vector<int> A&, B&, C&, D&, E&, F&, G&; // and so on...
}

and also so that

std::cout << one[0] << std::endl; // outputs 42 from outside the class...

But you refuse to answer unless you know why...

Why would anyone want to do this? Well, I don't really want to do it, but the application I had in mind was something like this. Suppose I'm building some kind of a data analysis tool, and I have clients or operations people who know basic logic and C++ syntax, but don't understand OOP or anything beyond CS 101. Things would go a lot smoother if they could write their own DoSomething()s on the fly, rather than communicate every need to developers. However, it's not realistic to get them to set up UNIX accounts, teach them how to compile, and so on. So suppose instead I'd like to build an intranet web interface that lets them write the body of DoSomething() and configure what datasets they'd like to "alias" by an uppercase char, and upon submission, generates C++ for a child class of Foo that overrides DoSomething(), then builds, runs, and returns the output. (Suspiciously specific for a "hypothetical," eh? :-) Okay, something like this situation does exist in my world—however it only inspired this idea and a desire to explore it—I don't think it'd be worth actually implementing.) Obviously, this whole uppercase char ordeal isn't absolutely necessary, but it'd be a nice touch because datasets are already associated with standard letters, e.g. P for Price, Q for Quantity, etc.

The best I can do...

To be honest, I can't figure out how I'd make this work using references. I prefer using references if possible, for these reasons.

With pointers, I guess I'd do this—

class Foo
{
public:
    template <typename ...Tail>
    Foo(std::pair<char, std::vector<int>*> head, Tail&&... tail)
    {
        std::vector<int>[26] vectors = {A, B, C, D, E, F, G}; // and so on...

        // I haven't learned how to use std::forward yet, but you get the picture...
        // And dear lord, forgive me for what I'm about to do...
        vectors[tail.first - 65] = tail.second;
    }

    virtual void DoSomething()
    {
        D->push_back(42);
        std::cout << (*D)[0] << std::endl;
    }
private:
    std::vector<int> A*, B*, C*, D*, E*, F*, G*; // and so on...
}

But even that is not that elegant.

  1. Is there a way to use references and achieve this?

  2. Is there a way to make this more generic, e.g. use pseudo-reflection methods to avoid having to list all the uppercase letters again?

  3. Any suggestions on alternative designs that would preserve the primary goal (the cosmetic aliasing I've described) in a more elegant or compact way?


Solution

  • You may use something like:

    class Foo
    {
    public:
        template <typename ...Ts>
        Foo(Ts&&... ts)  : m({std::forward<Ts>(ts)...}),
        A(m.at('A')),
        B(m.at('B'))
        // and so on...
        {
        }
    
        virtual void DoSomething()
        {
            A.push_back(42);
            std::cout << A[0] << std::endl;
        }
    private:
        std::map<char, std::vector<int>&> m;
        std::vector<int> &A, &B; //, &C, &D, &E, &F, &G; // and so on...
    };
    

    but that requires that each vector is given, so

    Foo(std::vector<int> (&v)[26]) : A(v[0]), B(v[1]) // and so on...
    {
    }
    

    or

    Foo(std::vector<int> &a, std::vector<int> &b /* And so on */) : A(a), B(b) // and so on...
    {
    }
    

    seems more appropriate.

    Live example.

    And it seems even simpler if it is the class Foo which owns the vector, so you will just have

    class Foo
    {
    public:
        virtual ~Foo() {}
        virtual void DoSomething() { /* Your code */ }
    
        std::vector<int>& getA() { return A; }
    private:
        std::vector<int> A, B, C, D; // And so on 
    };
    

    and provide getters to initialize internal vectors.

    and then

    std::vector<int>& one = foo.GetA(); // or foo.A if you let the members public.