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.