Можно ли создавать статические для цикла в С++?
Возможно ли что-то подобное существовать?
template<int Channel>
void deduce_mask(Matrix const &src, int mask[])
{
//I hope i could become a constant and the compiler would unroll the loop at compile time
for(int i = Channel; i != -1; --i)
{
//mapper is a helper class which translate two and three dimension into one dimension index
//constexpr makes it possible to find out the index at compile time
mask[mapper(0, 1, i)] = src(row - 1, col)[i];
mask[mapper(1, 1, i)] = src(row, col)[i];
mask[mapper(2, 1, i)] = src(row + 1, col)[i];
}
}
вместо
template<int Channel>
class deduceMask
{
public:
static void deduce_mask(matrix const &src, int mask[]);
};
template<int Channel>
void deduce_mask(matrix const &src, int mask[])
{
mask[mapper(0, 1, Channel)] = src(row - 1, col)[Channel];
mask[mapper(1, 1, Channel)] = src(row, col)[Channel];
mask[mapper(2, 1, Channel)] = src(row + 1, col)[Channel];
deduceMask<Channel - 1>::deduce_mask(src, mask);
}
template<>
class deduceMask<-1>
{
public:
static void deduce_mask(matrix const &src, int mask[])
{
}
};
Второе решение - это единственное решение, которое я могу предложить, когда хочу, чтобы компилятор выяснил результат во время компиляции. Есть ли у меня простой способ сделать "i" постоянным значением для решения метапрограммирования? Для меня с простым циклом for работать намного проще, чем с версией метапрограммирования.
Ответы
Ответ 1
Метапрограммирование шаблонов на С++ - это чисто функциональное программирование, и в чистом функциональном программировании вы не можете использовать циклы вроде или пока, и у вас вообще нет каких-либо изменяемых данных. Все, что у вас есть, - это рекурсия. Чтобы упростить работу с рекурсией, вам нужно немного повысить уровень абстракции. Рекурсивный код, который у вас есть, прекрасен, но итерацию и работу можно разделить:
template <int First, int Last>
struct static_for
{
template <typename Fn>
void operator()(Fn const& fn) const
{
if (First < Last)
{
fn(First);
static_for<First+1, Last>()(fn);
}
}
};
template <int N>
struct static_for<N, N>
{
template <typename Fn>
void operator()(Fn const& fn) const
{ }
};
Теперь, когда у вас есть эта мета-функция, вы можете написать свою функцию deduce_mask следующим образом:
template<int Channel>
void deduce_mask(Matrix const &src, int mask[])
{
static_for<0, Channel>()([&](int i)
{
mask[mapper(0, 1, i)] = src(row - 1, col)[i];
mask[mapper(1, 1, i)] = src(row, col)[i];
mask[mapper(2, 1, i)] = src(row + 1, col)[i];
});
}
Visual С++ 2012 с помощью командной строки /Ob 1 компилирует этот код в это:
push 0
call <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h)
push 1
call <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h)
push 2
call <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h)
push 3
call <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h)
push 4
call <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h)
...
Если вы не можете использовать лямбда-функции, вам нужно написать функтор. У Functor есть одно преимущество над лямбда-функцией - вы можете указать соглашение о вызове (если вы не возражаете против этого). Если оператор() функтора имеет __fastcall
вызов конвенции, то вы увидите mov edx, x
вместо push x
в коде ассемблера.
Ответ 2
С if constexpr
мы можем улучшить решение AOK.
template <int First, int Last, typename Lambda>
inline void static_for(Lambda const& f)
{
if constexpr (First < Last)
{
f(std::integral_constant<int, First>{});
static_for<First + 1, Last>(f);
}
}
С этим мы можем избавиться от этого ::apply
static_for<0, Channel>([&](auto i)
{
// code...
mask[mapper(0, 1, i)] = src(row - 1, col)[i]; // Notice that this does not change
std::get<i.value>(some_tuple); // But here you must get the member .value
// more code...
});
К сожалению, вам все равно придется писать i.value
.
Обратите внимание, что это было бы невозможно без if constexpr
, поскольку способ AOK потребовал бы частичной специализированности шаблонов static_for
.
Ответ 3
lego response, в то время как элегантный и потрясающий, не будет компилироваться, если вы хотите, чтобы индекс попадал в шаблон - например. std::get<i>(some_tuple)
В случае, если вы захотите реализовать эту дополнительную функцию в будущем, приведенный ниже код будет работать и должен быть обратно совместим с решением lego (кроме того, что я использую метод static apply вместо оператора()):
template <int First, int Last>
struct static_for
{
template <typename Lambda>
static inline constexpr void apply(Lambda const& f)
{
if (First < Last)
{
f(std::integral_constant<int, First>{});
static_for<First + 1, Last>::apply(f);
}
}
};
template <int N>
struct static_for<N, N>
{
template <typename Lambda>
static inline constexpr void apply(Lambda const& f) {}
};
Теперь вы можете сделать следующее:
static_for<0, Channel>::apply([&](auto i) // Changed from '(int i)'. In general, 'auto' will be a better choice for metaprogramming!
{
// code...
mask[mapper(0, 1, i)] = src(row - 1, col)[i]; // Notice that this does not change
std::get<i.value>(some_tuple); // But here you must get the member .value
// more code...
});
Протестировано в VС++ 2015. Я не исследовал, почему это работает, но я могу только предположить, что std::integral_constant<T,...>
определяет неявный приведение к T
с использованием значения, но компилятор не может понять, что неявное литье производит a constexpr
, поэтому вам нужно получить значение, используя i.value
, который является constexpr
.
Адресация @tom вопроса в комментарии
Если вы хотите итерации по пакету параметров, вы можете сделать следующее (такое же выполнение):
template<typename... Args>
inline constexpr auto foo(const Args&... args)
{
static_for<0,sizeof...(Args)>::apply([&](auto N)
{
std::cout << std::get<N.value>(std::make_tuple(args...));
});
}
foo(1,"a",2.5); // This does exactly what you think it would do
Если std::get<N.value>(std::make_tuple(args...))
выглядит уродливым, вы можете создать еще одну функцию constexpr
, которая минимизирует код.
Ответ 4
Вы должны быть в состоянии перебрать кортеж во время компиляции, используя for...
Насколько я понимаю, вы даже должны иметь возможность циклически проходить по элементам структуры следующим образом:
#include <iostream>
#include <string>
#include <tuple>
#include <utility>
struct Agg { int a;
std::string b;
double c;};
template <typename... T>
void print (std::tuple<T...> const & t) {
for... (auto const & member: t)
std::cout << member << '\n';
}
int main () {
auto agg = Agg{1,"bla",2.1};
print (agg);
}
Посмотрите это видео @47: 44 от Тимура Думлера. Обратите внимание, что даже gcc (или clang) из trunk (или master) пока не поддерживают этот цикл времени компиляции С++ 20 - поэтому я не тестировал. Кстати, кто-нибудь знает поисковик, где я могу искать for...
с пунктуацией?