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?
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.
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.
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.
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.
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.
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.
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.
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.
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.
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); });
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With