Мой код использует библиотеку (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>();
}
Шаблонные решения в Ответ Майлза безусловно, путь.
Но просто для удовольствия, вы также можете сделать это с препроцессором:
#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).
Основанное на выражении сгиба или рекурсивное шаблонное решение лучше. Но, если вам нужно решение для препроцессора, вы можете увидеть, обеспечивает ли ваш компилятор глубину включения в качестве предопределенного макроса. Например, 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
Этот метод ограничен вложенными пределами включения вашего компилятора.
Как я отмечал ранее в комментарии, это проще сделать с помощью шаблонов, чем с препроцессором.
Сначала определите шаблон
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()
это шаблон в первую очередь.