Можно ли создавать статические для цикла в С++? - программирование
Подтвердить что ты не робот

Можно ли создавать статические для цикла в С++?

Возможно ли что-то подобное существовать?

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 работать намного проще, чем с версией метапрограммирования.

4b9b3361

Ответ 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... с пунктуацией?