-1

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?

Community
  • 1
  • 1
Andrew Cheong
  • 29,362
  • 15
  • 90
  • 145
  • 1
    What's the purpose of characters `A`, `B` and `C`? After using them to initialize `foo`, the calling code never appears to refer to them again. Also, what is `one` in `cout << one[0]`? How, if at all, is it related to `foo`? – Igor Tandetnik Aug 19 '14 at 05:11
  • Note how they're used in `DoSomething()`—that's what I want to achieve. But now you've got me thinking. Here I am trying to map `char` to assign to its respective identifier, _i.e._ `'A'` assigns its paired vector to member `A&` or `A*`. Hm, instead, is there a way to use templates in a clever way... – Andrew Cheong Aug 19 '14 at 05:14
  • `DoSomething` uses the first element of the first vector - that could be stored in a single `int`. The fact that the character `A` was passed along with that vector appears irrelevant. – Igor Tandetnik Aug 19 '14 at 05:15
  • 3
    Are you looking for `map*>`, by any chance? – Igor Tandetnik Aug 19 '14 at 05:16
  • Perhaps I oversimplified my example. `DoSomething()` can use any of the letters, and it's not necessary that I'd construct with `'A'`, `'B'`, and `'C'` in that order—I might have assigned them to `'P'`, `'Q'`, and `'Z'`. – Andrew Cheong Aug 19 '14 at 05:16
  • How exactly would `DoSomething` determine which vector to use? Could you show a somewhat less oversimplified example? I, for one, no longer have any idea what you are trying to achieve. – Igor Tandetnik Aug 19 '14 at 05:17
  • I realize that I can use a `map`, but the primary goal here is the cosmetic aspect, as described in the "Why..." section. If it were _me_ doing the programming, then there's no need for these letters, and even if there were, I'd use a `map`, just as you said. However, I want to simplify the syntax for novice-level programmers so that they won't have to do `(*_map['P'])[i]` when they barely understand the STL, dereferencing, etc. It'd be nicer if the syntax exposed to them would be simply, `P[i]`. It seems trivial and it's easy to say, "Well, they should learn how to program," but that's why I – Andrew Cheong Aug 19 '14 at 05:20
  • disclaimed that this is largely a cosmetic issue, and probably not understandable by anyone not in the peculiar scenario I've experienced. It's all to satisfy more of a business goal (and my curiosity) than a most-efficient-or-logical-way goal. The reason such a syntax-makeover would help, is that they already write formulas in such terms, _e.g._ a Slow Stochastic Oscillator starts with something like `(C[i]-L[i])/(H[i]-L[i])`—which would be far more cumbersome if having to access map elements. I'd like them to be able to see formulas as they already see. – Andrew Cheong Aug 19 '14 at 05:21
  • Those characters are only known at run-time: `Foo foo(make_pair(char(rand()), one))`. You can't really expect a compile-time symbol to magically materialize in the past, for a character randomly generated in the future. – Igor Tandetnik Aug 19 '14 at 05:23
  • @IgorTandetnik - I understand that, and I know C++ doesn't have reflection capabilities. I figured that I have to at least declare the `A&` through `Z&`. My question is, what can be done besides that to make the solution more elegant or compact? (I appreciate you taking the time to look and ask questions, though!) – Andrew Cheong Aug 19 '14 at 05:25
  • Declare `A` through `Z` as pointers, write a giant switch statement in your constructor to assign to the right one based on the character. You can still pass references as parameters, take an address of them and store as pointers. Use delegating constructors to process `Tail` recursively. – Igor Tandetnik Aug 19 '14 at 05:27
  • Well, I suppose it should be possible to come up with some template cleverness that ultimately delegates to a 26-parameter constructor, which would bind each of its parameters to a corresponding letter (which could then be of a reference type). I don't feel like giving it a shot though; it's 1am around here. – Igor Tandetnik Aug 19 '14 at 05:34

1 Answers1

1

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.
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • The problem of incomplete map can be solved like this: `static vector skipped; Foo(map<...>& m) : A(m.count('A') ? m['A'] : skipped), ... {}` – Igor Tandetnik Aug 19 '14 at 11:19