2

Suppose the "standard" C++ inheritance paradigm:

struct GeneralFunc
{
  /*..members..*/
  virtual double value(double a, double b) { return 0; }
};

struct Func_classA : GeneralFunc
{
  /*..members..*/
  double value(double a, double b) { return a * b; } 
};

struct Func_classB : GeneralFunc
{
  /*..members..*/
  double value(double a, double b) { return a + b; }
};

void main(){
  double a = 1.0, b = 1.0;
  std::vector<GeneralFunc*> my_functions;
  //fill my_functions from input
  for (auto& f : my_functions)
  {
    double v = f->value(a, b);
  }
}

I would like an implementation that is most efficient for the iteration, i.e. minimizes indirect references, maximizes inline optimizations, ect. To constrain the problem, I know beforehand each specific "type" I want to implement (I can define only the "func" types I require, without having to allow other possibilities).

several options appear available:

boost::polycollection

#include <boost/poly_collection/base_collection.hpp>
//...rest the same
boost::base_collection<GeneralFunc> my_functions
//...rest the same

std::variant

#include <variant>
//...rts
using funcs = std::variant<Func_classA, Func_classB /*..possibly more../*>
std::vector<funcs> my_functions

or CRTP (Curiously Recurring Template Pattern) Let me know the correct nomenclature for this, but here I "upcast" the base class based on the "type" -- a kind of manual dispatch.

template<typename T>
struct GeneralFunc
{
  /*..members..*/
  int my_type;
  double value(double a, double b) {
    switch (my_type){
    case TYPE_A:
      return static_cast<Func_classA*>(this)->value(a,b);
  /*..you get the idea..*/

I'm okay sacrificing marginal efficiency for ease of development, but is there a consensus on the "best practice" in this case?

EDITS* fixed some typos; my current development is "in-development" of CRTP the last option.

SOLUTION:

After testing, both boost::polycollection and std::variant are valid approaches. However, this turned out to be far most efficient (from memory, may be slightly off).

enum ftype { A = 0, B, C };
struct GeneralFunc
{
  ftype my_type;
  GeneralFunc(ftype t) : my_type(t) {}
  inline double value(double a, double b) const; // delay definition until derived classes are defined
}

struct Func_classA : GeneralFunc
{
  Func_classA() : GeneralFunc(ftype::A) {}
  inline double value(double a, double b) const { return a * b; }
}
/* define B, C (& whatever) */

inline double GeneralFunc::value(double a, double b)
{
  switch(my_type){
    case (ftype::A):
      return static_cast<Func_classA*>(this)->value(a,b);
  /* same pattern for B, C, ect */
  }
}

void main(){
  std::vector<std::unique_ptr<GeneralFunc>> funcs;
  funcs.push_back(std::make_unique<Func_classA>());
  funcs.push_back(std::make_unique<Func_classB>());

  funcs[0]->value(1.0,1.0); // calls Func_classA.value
  funcs[1]->value(1.0,1.0); // calls Func_classB.value
}
Christopher Mauney
  • 459
  • 1
  • 5
  • 10
  • 1
    You miss virtual in Your base class – bartop Sep 27 '18 at 06:13
  • 1
    Normally there are other constraints that lead to one design or the other. I think that your original solution is already quite optimal. But you could define the function in the base class as `pure virtual` (should be at least `virtual` anyway). And, btw, your last example is not a CRTP. – Rene Sep 27 '18 at 06:20
  • And, finally, if your concern is speed/efficiency, then the standard answer is: Implement it, measure! – Rene Sep 27 '18 at 06:22
  • What CTRP is? Is it CRTP? – Evg Sep 27 '18 at 06:47
  • for my money, if possible I go with variant. – Richard Hodges Sep 27 '18 at 07:19

2 Answers2

1

I'd be tempted to just use std::function as the container, rather than re-writing it.

using GeneralFunc = std::function<double(double, double);

struct Func_classA
{
  /*..members..*/
  double value(double a, double b) { return a * b; } 
  /*explicit*/ operator GeneralFunc () const { return [this](double a, double b){ value(a, b) }; }
};

struct Func_classB
{
  /*..members..*/
  double value(double a, double b) { return a + b; }
  /*explicit*/ operator GeneralFunc () const { return [this](double a, double b){ value(a, b) }; } 
};

void main(){
  double a = 1.0, b = 1.0;
  std::vector<GeneralFunc> my_functions;
  //fill my_functions from input
  for (auto& f : my_functions)
  {
    double v = f(a, b);
  }
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
0

I think there's an option you didn't include (which is the one I'd use for performance critical code), that is to create a tuple of function objects and "iterate" over such tuple. Unfortunately there is no nice API to iterate over a tuple, so one has to implement his own. See the snippet below

#include <tuple>                                                                                                                                                                                   
#include <functional>                                                                                                                                                  

template<int ... Id, typename Functions>                                                                                                                             
auto apply(std::integer_sequence<int, Id ...>, Functions& my_functions, double& v, double a, double b){                                                          
    ([](auto a, auto b){a=b;}(v, std::get<Id>(my_functions)( a, b )), ...);                                                                                                                                     
}

int main(){                                                                                                                   
auto fA = [](double a, double b){return a*b;};                                                                                    
auto fB = [](double a, double b){return a+b;};                                                                                    
//create the tuple
auto my_functions=std::make_tuple(fA, fB);                                                                                                       
double v=0;                                                                                                    
double a = 1.;                                                                                                                      
double b = 1.;
//iterate over the tuple                                                                                                                                                                    
apply(std::make_integer_sequence<int, 2>(), my_functions, v, a, b);                                                                                                                                                      

}
This way you create a type safe zero overhead abstraction, since the compiler knows everything about the types you use (you don't need any type erasure mechanism). Also there's no need of virtual functions (same as in CRTP), so the compiler will probably inline the function calls. The snippet above uses C++17 generic lambdas, could be also implemented in C++14 or C++11 compliant way, but it would be more verbose. I would prefer this over CRTP because to me it looks more readable: no static cast to the derived class, and no artificial hierarchy of inheritance.

EDIT: from your answer looks like you don't really need the CRTP here, what you write using the CRTP solution is equivalent to this

enum ftype { A = 0, B, C };

auto fA = [](double a, double b){return a*b;};
auto fB = [](double a, double b){return a+b;};

int main(){

std::vector<ftype> types(2);
types[0]=A;
types[1]=B;

auto value = [&types](double a, double b, ftype i){
    switch(i){
    case (ftype::A):
    return fA(a,b);
    break;
    case (ftype::B):
    return fB(a,b);
    break;
    }
};

double v=value(1., 1., A);
v=value(1., 1., B);

}

Might be a matter of taste, but I think the version above is more readable (you don't really need a common base class, or static cast to the derived class).

Paolo Crosetto
  • 1,038
  • 7
  • 17
  • Op doesn't necessarily want all the functions exactly once – Caleth Sep 27 '18 at 08:52
  • This is not a constraint, one can change the "apply" to do other things, also depending on run-time parameters. The constraint is that information like the size of the container and the function objects must be available at compile time. The thing is that *if* such information is available at compile time, then using anything with dynamic dispatch (virtual functions, std::variant, std::function, function pointers) adds an overhead. The solutions with static dispatch (std::tuple and CRTP) are zero-overhead but they require the size of the container to be compile time known – Paolo Crosetto Sep 27 '18 at 09:20
  • 1
    Yes, but the sequence of functions sounds dynamic, as implied by the comment `//fill my_functions from input` – Caleth Sep 27 '18 at 09:27
  • 1
    What I understood is that the order in which the functions are called can be dynamic, but the function objects to be selected from is known at compile time (otherwise also the CRTP would not work). Anyway, @Christopher Mauney a clarification might be needed to address the question.. – Paolo Crosetto Sep 27 '18 at 09:59
  • The input would be from a parameter list in a file, e.g. (1,1,a), (5,2,a), (3,6,b) and so on. The number of these can be 100s to 1000s. (In truth, it's a reaction network, and I'm calculating reaction rates) – Christopher Mauney Sep 27 '18 at 13:28
  • Also, I may be misusing CRTP as a descriptor, but basically the last example uses vector and "upcasts" to call the appropriate subclass. A kind of "manual dispatch" – Christopher Mauney Sep 27 '18 at 13:42
  • I changed the answer adding some extra comments, in case you find it useful. It's an alternative solution which I find easier to read (but in the end it's a matter of taste...) – Paolo Crosetto Oct 01 '18 at 13:49