这个 for 循环可以用预处理器完成吗?
Can this for loop be done with the preprocessor?
我的代码使用了一个使用模板函数的库 (FastLED):
#define NUM_WIDGETS 4
Library.function<1>();
Library.function<2>();
Library.function<3>();
Library.function<4>();
我不能将其放入普通的 for 循环中,因为模板参数需要在编译时可计算。我可以在预处理器中执行此操作吗?还有其他建议吗?我希望能够方便地更改 NUM_WIDGETS 而无需复制粘贴这些行。
您可以在 std::index_sequence
的帮助下使用模板来完成此操作:
constexpr size_t NUM_WIDGETS = 4;
template <size_t N>
void foo() {
std::cout << N << '\n';
}
template <size_t... Ns>
void call_foo_helper(std::index_sequence<Ns...>) {
(foo<Ns>(), ...);
}
template <size_t N>
void call_foo() {
call_foo_helper(std::make_index_sequence<N>());
}
int main() {
call_foo<NUM_WIDGETS>();
}
这使用 C++17 折叠表达式。如果您没有可用的 C++17,则可以改用递归模板:
constexpr size_t NUM_WIDGETS = 4;
template <size_t N>
void foo() {
std::cout << N << '\n';
}
template <size_t N>
void call_foo() {
foo<N>();
call_foo<N - 1>();
}
template <>
void call_foo<0>() { }
int main() {
call_foo<NUM_WIDGETS>();
}
基于折叠表达式或递归模板的解决方案更胜一筹。但是,如果您想要一个预处理器解决方案,您可以查看您的编译器是否将包含深度作为预定义宏提供。例如,GCC 提供 __INCLUDE_LEVEL__
(而 MSVC 提供 __COUNTER__
)。然后,您可以使用递归包含达到一个限制来生成您的模板函数调用。
$ gcc -E r.cc | grep -v '^#' | sed '/^ *$/d'
int main()
{
Library.function<1>();
Library.function<2>();
Library.function<3>();
Library.function<4>();
}
$ cat r.cc
int main()
{
#define NUM_WIDGETS 4
#include "r.i"
}
$ cat r.i
#ifndef NUM_WIDGETS
# error "NUM_WIDGETS needs to be defined!"
#else
# if (__INCLUDE_LEVEL__) < ((NUM_WIDGETS) + 1)
Library.function<__INCLUDE_LEVEL__>();
# include __FILE__
# endif
#endif
此技术仅限于编译器的嵌套包含限制。
中的模板解决方案绝对是正确的选择。
但只是为了好玩,实际上你也可以用预处理器来做:
#include <iostream>
#include <boost/preprocessor/repetition/repeat.hpp>
template <int N> void function() { std::cout << N << "\n"; }
#define NUM_WIDGETS 4
int main()
{
#define CALL_FUNC(z,n,d) function<n+1>();
BOOST_PP_REPEAT(NUM_WIDGETS, CALL_FUNC, ~)
#undef CALL_FUNC
}
这不太好,因为计数 NUM_WIDGETS
需要预处理器知道为纯数字字符串。不是 (4)
,不是 4U
,不是 constexpr
变量,等等。并且 Boost.Preprocessor 确实有 256 次重复的限制(如果使用 MSVC 则略少)。
正如我之前在评论中指出的那样,使用模板比使用预处理器更容易做到这一点。
首先定义一个模板
template<unsigned n> void invoke() {Library.function<n>(); invoke<n-1>();};
然后专门化一个什么都不做的结束条件
template<> void invoke<0U>() {};
并称之为
int main()
{
invoke<NUM_WIDGETS>();
}
这将以与指定顺序相反的顺序调用函数。这可以通过将模板函数更改为
来解决
template<unsigned n> void invoke() {Library.function<NUM_WIDGETS + 1 - n>(); invoke<n-1>();};
并保持相同的专业化以结束递归。
当然,如果您需要做这样的技巧(无论是使用预处理器还是模板),这表明设计有问题,您应该首先问为什么 Library.function()
是模板。
我的代码使用了一个使用模板函数的库 (FastLED):
#define NUM_WIDGETS 4
Library.function<1>();
Library.function<2>();
Library.function<3>();
Library.function<4>();
我不能将其放入普通的 for 循环中,因为模板参数需要在编译时可计算。我可以在预处理器中执行此操作吗?还有其他建议吗?我希望能够方便地更改 NUM_WIDGETS 而无需复制粘贴这些行。
您可以在 std::index_sequence
的帮助下使用模板来完成此操作:
constexpr size_t NUM_WIDGETS = 4;
template <size_t N>
void foo() {
std::cout << N << '\n';
}
template <size_t... Ns>
void call_foo_helper(std::index_sequence<Ns...>) {
(foo<Ns>(), ...);
}
template <size_t N>
void call_foo() {
call_foo_helper(std::make_index_sequence<N>());
}
int main() {
call_foo<NUM_WIDGETS>();
}
这使用 C++17 折叠表达式。如果您没有可用的 C++17,则可以改用递归模板:
constexpr size_t NUM_WIDGETS = 4;
template <size_t N>
void foo() {
std::cout << N << '\n';
}
template <size_t N>
void call_foo() {
foo<N>();
call_foo<N - 1>();
}
template <>
void call_foo<0>() { }
int main() {
call_foo<NUM_WIDGETS>();
}
基于折叠表达式或递归模板的解决方案更胜一筹。但是,如果您想要一个预处理器解决方案,您可以查看您的编译器是否将包含深度作为预定义宏提供。例如,GCC 提供 __INCLUDE_LEVEL__
(而 MSVC 提供 __COUNTER__
)。然后,您可以使用递归包含达到一个限制来生成您的模板函数调用。
$ gcc -E r.cc | grep -v '^#' | sed '/^ *$/d'
int main()
{
Library.function<1>();
Library.function<2>();
Library.function<3>();
Library.function<4>();
}
$ cat r.cc
int main()
{
#define NUM_WIDGETS 4
#include "r.i"
}
$ cat r.i
#ifndef NUM_WIDGETS
# error "NUM_WIDGETS needs to be defined!"
#else
# if (__INCLUDE_LEVEL__) < ((NUM_WIDGETS) + 1)
Library.function<__INCLUDE_LEVEL__>();
# include __FILE__
# endif
#endif
此技术仅限于编译器的嵌套包含限制。
但只是为了好玩,实际上你也可以用预处理器来做:
#include <iostream>
#include <boost/preprocessor/repetition/repeat.hpp>
template <int N> void function() { std::cout << N << "\n"; }
#define NUM_WIDGETS 4
int main()
{
#define CALL_FUNC(z,n,d) function<n+1>();
BOOST_PP_REPEAT(NUM_WIDGETS, CALL_FUNC, ~)
#undef CALL_FUNC
}
这不太好,因为计数 NUM_WIDGETS
需要预处理器知道为纯数字字符串。不是 (4)
,不是 4U
,不是 constexpr
变量,等等。并且 Boost.Preprocessor 确实有 256 次重复的限制(如果使用 MSVC 则略少)。
正如我之前在评论中指出的那样,使用模板比使用预处理器更容易做到这一点。
首先定义一个模板
template<unsigned n> void invoke() {Library.function<n>(); invoke<n-1>();};
然后专门化一个什么都不做的结束条件
template<> void invoke<0U>() {};
并称之为
int main()
{
invoke<NUM_WIDGETS>();
}
这将以与指定顺序相反的顺序调用函数。这可以通过将模板函数更改为
来解决 template<unsigned n> void invoke() {Library.function<NUM_WIDGETS + 1 - n>(); invoke<n-1>();};
并保持相同的专业化以结束递归。
当然,如果您需要做这样的技巧(无论是使用预处理器还是模板),这表明设计有问题,您应该首先问为什么 Library.function()
是模板。