Is there a C++ idiom for encoding so-called rate-groups (realtime computing) into a function via template magic?

Viewed 91

In realtime computing, there is sometimes a concept of a rate-group. This is essentially the frequency at which a given function will be called. (e.g. RG 10 might imply executing every 10ms).

I'm wondering if there's a nice way in C++ to encode this property via template magic like so:

template<int rate_group> void integrate( float& some_integral, float spot_value );

The above function could be instantiated as follows:

while(true) // outer control loop
{
     // do something
     integrate<10>( error_sum, sense_feedback );
     
     for( int i = 0 ; i < 10 ; i++) // inner control loop
     {
          integrate<1>( error_sum_2, some_other_sense );

          // sleep 1ms
     }
}

The above is a toy scenario and I'm simply exploring if there's an existing idiom to do this kind of thing. I think the most important property I'd like to get from a templated approach like this is property transitivity: any function with a rategroup rate_group would automatically (at compile time) resolve to the correctly specialized sub-routines that it calls. (e.g. a call from within integrate<10>() to some other templated function would automatically use the correct rate group).


Edit: Here's a more fleshed out description of some desireable properties.

  1. probably the single biggest feature would be design time compiler warnings. Let's say a particular function foo is not allowed in a fast rate group, I want to ensure that any top-level function tree I call never calls foo<10> but is allowed to call foo<100>. The above really requires that there be a mechanism to instantiate templates by context. E.g. a function with template<int rate_group> would automatically instantiate any functions it calls with the rate group.

Examples:

template<int rate_group> void foo( /* ... */ );
template<int rate_group> void bar( /* ... */ );
template<int rate_group> void baz( /* ... */ );

/* impl */
void foo<10>( /* ... */ ){ std::static_assert(false, "this method cannot be called at this rate group"); };

template<int rate_group> void bar( /* ... */ )
{
    // do stuff
    foo();
}

Using bar<100> should work just fine, but trying to build bar<10> should result in a compile-time assertion.

The key for the above feature to function is being able to give any C++ block a contextual rate-group attribute and by default pass that on to any calls made within that block.

  1. building on top of #1, templates would have an ordering of rate groups to disallow using slower than self rate-group functions:

    void foo<50>( /* ... */ )
    {
        bar<10>();  // OK - this is a higher rate group, and would be allowed
        bar<50>();  // OK - same rate-group
        bar<100>(); // NO - this could be a slow function - fail compilation
    }
    

These two features alone would already go a long way. However, both depend on the automated maintenance of the property. I'm not sure C++ allows for this level of meta-coding.

1 Answers

I don't know idiom for your problem, but you might indeed use template to handle that.

With your restrictions:

  • Inner functions should not call bigger rate.
  • Possibility to limit function in given range

I would go with template class

template <std::size_t N>
class RateGroup
{
    friend int main(int, char**); // Give only one entry point
                                  // to create main rates.
 
    Rate() = default;
public:
    Rate(const& Rate) = default;

    template <std::size_t N2>
    RateGroup<N2> New() const requires (N2 <= N) { return {}; }
    // Or SFINAE or static_assert instead of C++20 requires.
};

Then you function might be

template<std::size_t N>
void foo(RateGroup<N> /* ... */ ) requires (N > 10);
template<std::size_t N> void bar(RateGroup<N> rateGroup,  /* ... */ )
{
    // ...
    foo(rateGroup /*, ...*/);
}

inline void baz(RateGroup<50> rateGroup /*, ... */ )
{
    bar(rateGroup); // Same one, so 50.
    bar(rateGroup.New<10>());  // OK, 10 < 50.
    bar(rateGroup.New<100>()); // Fail to compile as expected, !(100 < 50).
    bar(RateGroup<100>()); // Fail to compile: constructor is restricted to allowed functions

}

You can so have fixed RateGroup or templated ones for your functions.

Related