Home > Software engineering >  Executing a generic function every N times with a static variable
Executing a generic function every N times with a static variable

Time:10-21

I am trying to write a wrapper function that executes a given function every N times (something similar to Google logging's LOG_EVERY_N).

What I have done so far is:

#include <cstddef>
#include <functional>
#include <utility>

template<size_t N, typename Callable, typename... Args>
void call_every_n(Callable&& c, Args... args)
{
    static size_t __counter = 0;
    if(__counter == N - 1)
    {
        std::invoke(std::forward<Callable>(c), std::forward<Args>(args)...);
        __counter = 0;
    } else   __counter;
}

#define CALL_EVERY_N(N, FUNC, ...) call_every_n<N, decltype(&FUNC), decltype(__VA_ARGS__)>(FUNC, __VA_ARGS__)

The problem is with the usage of the static variable for the internal counter but I cannot figure out how to achieve this differently. In fact, this code works as expected until I call the same function with the same N.

This is an example which demonstrates the issue:

#include <iostream>

void test(int a, int num)
{
    std::cout << num << " = " << a << "\n";
}

int main() {

  for(int i = 1; i < 20;   i)
  {
      CALL_EVERY_N(3, test, i, 1);
      CALL_EVERY_N(3, test, i, 2);
  }

    return 0;
}

This code outputs:

1 = 2
2 = 3
1 = 5
2 = 6
1 = 8
2 = 9
1 = 11
2 = 12
1 = 14
2 = 15
1 = 17
2 = 18

i.e. the exact same static variable is used (and then modified) by the two different functions.

I also would like to enforce the fact that Callable must be a function returning void, how could I do it?

CodePudding user response:

Every lambda expression is of different type, hence you can use it as a tag:

#include <cstddef>
#include <functional>
#include <iostream>

template<size_t N, typename Callable,typename Dummy, typename... Args>
void call_every_n(Dummy,Callable&& c,Args&&... args)
{
    static size_t counter = 0;
    if(counter == N - 1)
    {
        std::invoke(std::forward<Callable>(c), std::forward<Args>(args)...);
        counter = 0;
    } else   counter;
}

#define CALL_EVERY_N(N, FUNC, ...) call_every_n<N>([]{},FUNC, __VA_ARGS__)
// in each expansion, this is of different type    ^^
void test(int a, int num)
{
    std::cout << num << " = " << a << "\n";
}

int main() {

  for(int i = 1; i < 20;   i)
  {
      CALL_EVERY_N(3, test, i, 1);
      CALL_EVERY_N(3, test, i, 2);
  }

    return 0;
}

Output:

1 = 3
2 = 3
1 = 6
2 = 6
1 = 9
2 = 9
1 = 12
2 = 12
1 = 15
2 = 15
1 = 18
2 = 18

Only since C 20 the lambda expression can appear in unevaluated context (decltype), before a dummy parameter needs to be passed to deduce its type. You don't need to use decltype for the others, the template parameters can be deduced from the functions parameters. Perfect forwarding via std::forward requires universal references (ie Args&&).

Note that names starting with __ are reserved for the implementation. Don't use them.

CodePudding user response:

Another approach is to create a counter variable based on the line the CALL_EVERY macro is invoked and pass that in to call_every, like so:

#include <iostream>

//------------------------------------------------------------------------------------------------
// template for calling a function, using lambda expresion

template<typename fn_t>
int call_every(int times, int n, fn_t fn)
{
    if (n % times == 0)
    {
        fn();
    }

    return n 1;
}

//------------------------------------------------------------------------------------------------
// helpers to create a variable for each CALL_EVERY based on line number
#define CAT1(a, b) a##b
#define CAT(a, b) CAT1(a, b)
#define VARIABLE_NAME(prefix) CAT(prefix,__LINE__)

// the full macro
#define CALL_EVERY(N, fn) \
     static int VARIABLE_NAME(call_every_counter_) {0}; \
     VARIABLE_NAME(call_every_counter_) = call_every(N, VARIABLE_NAME(call_every_counter_), (fn));

//------------------------------------------------------------------------------------------------

void test(int a, int num)
{
    std::cout << num << " = " << a << "\n";
}

int main()
{
    for (int i = 0; i < 20;   i)
    {
        CALL_EVERY(3, [&i] { test(i, 3); });
        CALL_EVERY(5, [&i] { test(i, 5); });
    }
}

CodePudding user response:

CALL_EVERY_N can create the static object:

template <std::size_t N>
struct call_every_n
{
    template <typename Callable, typename... Args>    
    void operator()(Callable&& c, Args&&... args)
    {
        if (  counter >= N) {
            std::invoke(std::forward<Callable>(c), std::forward<Args>(args)...);
            counter = 0;
        }
    }
    std::size_t counter = 0;  
};

#define CALL_EVERY_N(N, FUNC, ...) do { static call_every_n<N> call_every_n_var; call_every_n_var(FUNC, __VA_ARGS__); } while(0)

Demo.

  • Related