0

I am currently making a framework for processing numerical data. I started with a class Collection, which has the so-called attributes, which are simply std::vectors used to contain the data from the source (actually std::variants of them, since the data may be integer or floating). A typical Collection, in this case representing a group of electrons produced in a collision event, looks like this:

  Collection<int, float> electron("electron", ...);
  electron.add_attribute("px", ...);
  electron.add_attribute("py", ...);
  electron.add_attribute("pz", ...);
  electron.add_attribute("charge", ...);

where the ... are just std::string arguments related to data source, which are stored in the class. Actual readout of the source happens later in the code, when all the Collections have been defined. There are also other methods related to filtering, sorting etc.

Now I'm looking to define another class, let's call them Aggregate, which is supposed to contain data that are transformations from multiple Collections (but otherwise works similarly). To this end, I plan to have the Aggregate save references to the relevant Collections and then have the Aggregate's add_attribute method take a lambda for the relevant transformation. And this is where I'm stuck.

Specifically, how do I store the lambda? For Collection I store the source-related strings into a member std::vector, but clearly this doesn't work for lambdas. I've considered std::function but the problem is that the lambdas won't have the same signature, since if we consider say, a dielectron Aggregate of two electron Collections, the charge attribute of the dielectron is just charge1 + charge2, while the mass attribute is considerably more complicated. This is how I imagine it would look like on the user end:

  auto sum_of_two = [] (auto p1, auto p2) { return p1 + p2; };
  auto energy_of_two = [] (auto px1, auto py1, auto pz1, 
                           auto px2, auto py2, auto pz2) { 
                    auto ee1 = (px1 * px1) + (py1 * py1) + (pz1 * pz1); 
                    auto ee2 = (px2 * px2) + (py2 * py2) + (pz2 * pz2); 
                    return std::sqrt(ee1) + std::sqrt(ee2); }

  Aggregate<int, float> dielectron("dielectron", electron, electron);
  dielectron.add_attribute("px", sum_of_two, "electron::px", "electron::px");
  dielectron.add_attribute("py", sum_of_two, "electron::py", "electron::py");
  dielectron.add_attribute("pz", sum_of_two, "electron::pz", "electron::pz");
  dielectron.add_attribute("charge", sum_of_two, "electron::charge", "electron::charge");
  dielectron.add_attribute("energy", energy_of_two, "electron::px", "electron::py", "electron::pz", "electron::px", "electron::py", "electron::pz");

Of course, the problem is the developer end. The signature of add_attribute can be written with variadics, but its body leaves me stuck. In trying to save the lambda, I tried wrapping it inside a struct that inherits from an empty base, but I've always been unable to either store or use them (or it just refuses to compile). I have considered using std::tuples, but I see some problems with the approach:

1- I don't know how to write the class template in this case (probably easy)

2- It forces the lambda types to be part of the Aggregate class template, which makes checking for type overlap much harder, which I do to ensure the contributing Collection are all consistent with each other

3- Due to the above, the tuples and therefore attributes are likely to have to be initialized together with the Aggregate instance, which seems rather unattractive to me (no add_attribute() for example)

Am I just not arriving at the right syntax or is this really not possible in C++17? Btw I'm focusing on lambda is only because that's the initial idea, if there are other ways to do this, I'd be happy to know them.

afiqaize
  • 1
  • 1
  • I believe the difference with those answers is he doesn't known the function signature of the lambda. It can be different, e.g. compare `sum_of_two` vs `energy_of_two`, those can't both be used to construct the same `std::function<>`. – TrentP Mar 04 '20 at 17:30
  • 1. There is way to much text explaining things that is not relevant to your question. 2. Taking parameters in the lambda might not be the right approach. If you instead just pass references to the electrons, you can let the lambda retrieve whatever data it needs from it in the body and maintain a uniform signature for all the lambdas. – super Mar 04 '20 at 17:30
  • 1. Maybe, but I just wanted to be sure that I get the intention across to prevent possible XY problems. 2. The problem is that in that case the lambdas users need to supply would be much more involved, requiring std::visit internally, which I don't think is feasible – afiqaize Mar 04 '20 at 17:43
  • @afiqaize No. You can use the same syntax you have now. Then inside that template member function, create a new lambda that only takes references to the electron, retrieves the values passed in and store that lambda (with it's uniform signature) in a vector. – super Mar 04 '20 at 18:31
  • @super Ah hmm. Didn't think about that before. I need to experiment around to see it more clearly. Thanks for the pointer. – afiqaize Mar 04 '20 at 18:46
  • It might help to think about the *set* of signatures that you want `Aggregate` to support. If it’s really an unbounded number of attributes from the underlying `Collection`, maybe you should define one signature in terms of a `vector`. (But it’s probably always possible to use the two-electron signature already mentioned.) – Davis Herring Mar 04 '20 at 19:49

0 Answers0