Best practice implementation of functions-family in cpp

112 Views Asked by At

Preliminary

A functions-family is a countable list of functions y=f_i(x), for a counter i=0,...,n-1 for some integer n.

Minimum example

I have a derived struct in c++ that shall define such a functions-family for n=3:

template<typename Tfloat, size_t n> Base{
  template<size_t i> bool f(Tfloat* x){ static_assert(i<n); return false; }
};

template<typename Tfloat>
struct Derived: Base<Tfloat,3>{
  template<> bool f<0>(Tfloat* x){ x[3]-=x[2]; return true; }
  template<> bool f<1>(Tfloat* x){ x[1]*=x[3]; return true; }
  template<> bool f<2>(Tfloat* x){ if(x[5]==0) return false; x[2]/=x[5]; return true; }
};

Question

How can I achieve the following?:

  1. force by virtue of inheritance from Base<Tfloat,3> at compile-time that all three functions of the family are defined.
  2. I need to be able to statically loop through f with, e.g. an std::sequence<n>. Until now, I have given a different name f0, f1,... to each function. But then one must manually write an if constexpr(i=0){ return f0(x); } wrapper to traverse through all names in the definition of template<size_t i> f(Tfloat* x). Acutally, this is how I am doing it as of now.
  3. Be able to write the definitions within the body of Derived.

Issues

I know that there are a dozen of issues:

  • one cannot specialize a template function within the scope of Derived.
  • one cannot even specialize a template function of an un-specialized Derived at all.
  • and while a call of f<i> for i>=n can be statically prohibited, I see no way to enforce definedness for all i=0,...,n-1 .

Thus I am very curious for your ideas.

Remarks

  • Typically, the functions for different i have very little to do with one another; e.g., physical models for the i-th stage of a rocket. I prefer the use of template for size_t i.
  • Derived typically holds run-time dependent parameters and data computed/initialized from these parameters. Hence, the use of a namespace instead of struct for Derived appears unsuitable to me.

Proposal: Function Array

Taken from @HolyBlackCat's comment:

#include<iostream>
#include<functional>
#include<array>

template<typename Tfloat, size_t n>
class Base{
protected:
    std::array<std::function<bool(Tfloat*)>,n> f_;
public:
    // probably here in conjunction with an invalid default initialize for each element of f_, CRTP could be used to assert after construction of derived that each f_[i] is defined?
    template<size_t i>
    bool f(Tfloat* x){ return f_[i](x); } // accessor prevents external overwrite into f_.
};

template<typename Tfloat>
class Derived: public Base<Tfloat,3>{
public:
    //
    Derived(){
        Base<Tfloat,3>::f_[0] = [this](Tfloat* x){
            x[0] *= x[1];
            return true;
        };
    }
    //
};

template<typename Tfloat>
struct NaiveDerived{
    //
    bool f0(Tfloat* x){
        x[0] *= x[1];
        return true;
    }
    //
};

int main(){
    Derived<float> d;
    float x[10];
    d.f[0](x);
}

This code works and achieves the requirements 2 and 3.

I do not like the use of a function array much because eventually we want to marry two things:

  • a function body of the i-th function { x[0]*=x[1]; return true; }
  • a template function name of the i-th function f<i>(x)

Introducing f_ into the mix only accomplishes to avoid giving names to each function body before we assign said body to its respective f<i>. More readable would be a solution that states the i-th function body directly with f<i>.

2

There are 2 best solutions below

1
Jarod42 On BEST ANSWER

If you might change signature, i.e calling d.f(IC<0>{}, x); and not d.f<0>(x);

Following might help, using virtual to ensure functions existence:

// shorten code
template <std::size_t N>
using IC = std::integral_constant<std::size_t, N>;

// helper node I
template <typename T, size_t I> struct BaseNode
{
    virtual ~BaseNode() = default;
    virtual bool f(IC<I>, T* x) = 0;
};

template<typename T, typename Seq> struct BaseImpl;

template<typename T, std::size_t... Is>
struct BaseImpl<T, std::index_sequence<Is...>> : BaseNode<T, Is>...
{
    using BaseNode<T, Is>::f...;
};

// Use expecting name
template<typename T, std::size_t N>
using Base = BaseImpl<T, std::make_index_sequence<N>>;

//  The derived class
template<typename Tfloat>
struct Derived : Base<Tfloat,3>{
  bool f(IC<0>, Tfloat* x) override { x[3]-=x[2]; return true; }
  bool f(IC<1>, Tfloat* x) override { x[1]*=x[3]; return true; }
  bool f(IC<2>, Tfloat* x) override { if(x[5]==0) return false; x[2]/=x[5]; return true; }
};

Demo

2
Red.Wave On

Check this:

template <std::size_t n> 
using index_constant = std::integral_constant<std::size_t, n>;
template<typename arg, std::size_t m>
struct base{
    template<std::size_t n>
    requires (n < m)
    bool f(this auto& self, index_constant<n> i, arg val){ return self.f_impl(i, std::forward<arg>(val));
    /*~base(this auto& self){
         [&]<std::size_t...i>(std::index_sequence<i...>)
         {static_assert((
              std::is_same_v<
                   bool,
                   decltype(self.f_impl(index_constant<i>{}, std::declval<arg>()))
              >&&...
         ));} (std::make_index_sequence<m>{});
    };*/
};

struct derived: base<float, 3>{
     template<std::size_t n>
     bool f_impl(index_constant<n>, float){ return true; };
};

/*...*/
derived d;
bool flag=[&d]<std::size_t ...i>(std::index_sequence<i...>)
    {return (d.f(index_constant<i>,1.0f) && ...);}
    (std::make_index_sequence<3>{});

In the base class, I have used the C++ deduced this feature to simplify CRTP, and avoid the verbose usage of derived. You can work your way out of compile time traversal of indices through fold expressions and generic lambdas as shown in the sample above.

Edit:

Constructor and destructor are not the best place to detect the actual type. It was a folly on my side.