使用静态变量每 N 次执行一次泛型函数

Jac*_*ero 15 c++ templates

我正在尝试编写一个包装函数,该函数每 N 次执行一次给定函数(类似于 Google 日志记录的 LOG_EVERY_N)。

到目前为止我所做的是:

#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__)
Run Code Online (Sandbox Code Playgroud)

问题在于内部计数器使用静态变量,但我不知道如何以不同的方式实现这一点。事实上,这段代码会按预期工作,直到我使用相同的 N 调用相同的函数。

这是一个演示该问题的示例:

#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;
}
Run Code Online (Sandbox Code Playgroud)

该代码输出:

1 = 2
2 = 3
1 = 5
2 = 6
1 = 8
2 = 9
1 = 11
2 = 12
1 = 14
2 = 15
1 = 17
2 = 18
Run Code Online (Sandbox Code Playgroud)

即两个不同的函数使用(然后修改)完全相同的静态变量。

我还想强制 Callable 必须是一个返回 void 的函数,我该怎么做呢?

for*_*818 10

每个 lambda 表达式都有不同的类型,因此您可以将其用作标签:

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出

1 = 3
2 = 3
1 = 6
2 = 6
1 = 9
2 = 9
1 = 12
2 = 12
1 = 15
2 = 15
1 = 18
2 = 18
Run Code Online (Sandbox Code Playgroud)

仅自 C++20 起,decltype在需要传递虚拟参数以推断其类型之前,lambda 表达式才能出现在未计算的上下文 ( ) 中。您不需要使用decltype其他参数,模板参数可以从函数参数中推导出来。完美转发通过std::forward需要通用引用(即Args&&)。

请注意,以 开头的名称__是为实现而保留的。不要使用它们。


P K*_*mer 6

另一种方法是根据调用 CALL_EVERY 宏的行创建一个计数器变量,并将其传递给 call_every,如下所示:

#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); });
    }
}
Run Code Online (Sandbox Code Playgroud)