Можно ли сделать это для цикла с препроцессором?

Мой код использует библиотеку (FastLED), которая использует шаблонную функцию:

#define NUM_WIDGETS 4

Library.function<1>();
Library.function<2>();
Library.function<3>();
Library.function<4>();

Я не могу поместить это в обычный цикл for, так как аргумент шаблона должен быть вычислим во время компиляции. Могу ли я сделать это в препроцессоре? Любое другое предложение? Я хотел бы иметь возможность легко изменять NUM_WIDGETS без копирования этих строк.

1

Решение

Вы можете сделать это с помощью шаблонов с помощью 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>();
}
5

Другие решения

Шаблонные решения в Ответ Майлза безусловно, путь.

Но просто для удовольствия, вы также можете сделать это с препроцессором:

#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).

1

Основанное на выражении сгиба или рекурсивное шаблонное решение лучше. Но, если вам нужно решение для препроцессора, вы можете увидеть, обеспечивает ли ваш компилятор глубину включения в качестве предопределенного макроса. Например, 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

Этот метод ограничен вложенными пределами включения вашего компилятора.

1

Как я отмечал ранее в комментарии, это проще сделать с помощью шаблонов, чем с препроцессором.

Сначала определите шаблон

 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() это шаблон в первую очередь.

1