Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Executing a generic function every N times with a static variable

Tags:

c++

templates

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?

like image 322
Jacopo Lottero Avatar asked Oct 05 '21 10:10

Jacopo Lottero


People also ask

How do you declare a static variable in a function?

A static variable is a variable that is declared using the keyword static. The space for the static variable is allocated only one time and this is used for the entirety of the program. Once this variable is declared, it exists till the program executes.

Can we use static variable in main function?

Static variables in methods The variables with in a method are local variables and their scope lies within the method and they get destroyed after the method execution. i.e. you cannot use a local variable outside the current method which contradicts with the definition of class/static variable.

Can we change static variable value in C?

When static keyword is used, variable or data members or functions can not be modified again. It is allocated for the lifetime of program. Static functions can be called directly by using class name. Static variables are initialized only once.

What is static local variable in C?

A local static variable is a variable, whose lifetime doesn't stop with a function call where it is declared. It extends until the lifetime of a complete program. All function calls share the same copy of local static variables. These variables are used to count the number of times a function is called.

What happens to a static int variable when it is declared?

1) A static int variable remains in memory while the program is running. A normal or auto variable is destroyed when a function call where the variable was declared is over.

What is the difference between static variable and normal variable?

Hence, static variables preserve their previous value in their previous scope and are not initialized again in the new scope. 1) A static int variable remains in memory while the program is running. A normal or auto variable is destroyed when a function call where the variable was declared is over.

What happens to an auto variable when a function is called?

A normal or auto variable is destroyed when a function call where the variable was declared is over. For example, we can use static int to count a number of times a function is called, but an auto variable can’t be used for this purpose. Take a step-up from those "Hello World" programs.

What are static global variables and functions in C++?

5) Static global variables and functions are also possible in C/C++. The purpose of these is to limit scope of a variable or function to a file. Please refer Static functions in C for more details.


3 Answers

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.

like image 83
463035818_is_not_a_number Avatar answered Oct 20 '22 02:10

463035818_is_not_a_number


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); });
    }
}
like image 23
Pepijn Kramer Avatar answered Oct 20 '22 00:10

Pepijn Kramer


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.

like image 4
Jarod42 Avatar answered Oct 20 '22 02:10

Jarod42