2012-12-11 14 views
15

Czy to możliwe, że coś takiego istnieje?Czy jest możliwe rozwinięcie statycznej pętli w C++?

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];  
    } 
} 

zamiast

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[]) 
    { 

    } 
}; 

Drugie rozwiązanie jest jedynym rozwiązaniem mogłem wymyślić, kiedy chcę kompilator, aby dowiedzieć wynik na kompilacji time.Do mam łatwą drogę do sprawiają, że "i" staje się wartością stałą, jak metaprogramming solution? Dla mnie prosta pętla for jest znacznie łatwiejsza w pracy niż w wersji metaprogramming.

Przepraszam za mój biedny angielski, mam nadzieję, że wyjaśnię właściwie swój problem.

+2

Możesz również napisać rekursywnie i użyć constexpr, jeśli wolisz ten typ składni? – Agentlien

+0

Próbowałem utworzyć wersję constexpr, ale nie powiodło się, constexpr pozwala tylko na jedną instrukcję return. – StereoMatching

+3

Jestem dość pewny, że większość współczesnych kompilatorów automatycznie wykonuje tę optymalizację, podobnie jak robią to dla pętli 'for' aż do stałej wartości (np.' For (int i = 0; i <5; i ++) '). Musisz jednak sprawdzić, żeby się upewnić. – ShdNx

Odpowiedz

20

Metaprogramowanie szablonów w C++ to programowanie czysto funkcjonalne, aw czystym programowaniu funkcjonalnym nie można używać pętli jak na przykład lub w czasie i nie można uzyskać żadnych zmiennych danych w ogóle. Wszystko, co masz, to rekursja. Aby ułatwić pracę z rekurencją, musisz nieco podnieść poziom abstrakcji. Rekurencyjne kod, który masz jest w porządku, ale iteracja i praca może być rozpadł:

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 
    { } 
}; 

Teraz, że masz to meta-funkcji, można napisać funkcję deduce_mask tak:

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 C++ 2012 z/OB1 przełącznik linii poleceń kompiluje kod na to:

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

Jeśli nie można korzystać z funkcji lambda, trzeba napisać funktor. Funktor ma jedną przewagę nad funkcją lambda - możesz określić konwencję wywołania (jeśli nie masz nic przeciwko temu). Jeśli operator() funktora ma konwencję wywoływania __fastcall, to zamiast push x w kodzie asemblera pojawi się mov edx, x.

+0

Dzięki, ta odpowiedź jest całkiem elegancka (przynajmniej dla mnie). – StereoMatching

+5

Ale czy nie wszystkie te "połączenia" są wolniejsze od normalnej pętli for? Dlaczego kompilator ich nie optymalizuje? – Kapichu

2

Odpowiedź lego, elegancka i niesamowita, nie zostanie skompilowana, jeśli chcesz, aby indeks wszedł do szablonu - np. std::get<i>(some_tuple)

W przypadku, gdy chcesz, aby wdrożyć tę dodatkową funkcję w przyszłości poniższy kod działa i powinien być kompatybilny wstecz z roztworem LEGO (z wyjątkiem, że używam statyczną stosuje metodę zamiast operatora()):

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) {} 
}; 

teraz można wykonać następujące czynności:

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... 
}); 

Testowany w VC++ 2015. nie badaniom dlaczego to działa, ale mogę tylko przypuszczać, że std::integral_constant<T,...> definiuje niejawny oddanych do T użyciu va lue, ale kompilator nie może odgadnąć, że niejawny rzut daje constexpr, więc musisz odzyskać wartość, używając i.value, która jest constexpr.

Adresowanie @ pytaniem Toma w komentarzu Jeśli chcesz iteracyjne nad paczkę parametru, można wykonać następujące czynności (tak samo wdrożenie):

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 

Jeśli std::get<N.value>(std::make_tuple(args...)) wygląda brzydko, można utworzyć inny constexpr funkcja, która minimalizuje kod.

+0

Myślę, że powinieneś zmienić '(int i)' na '(auto i)', ponieważ 'int :: value' jest źle sformułowany – Caleth

+0

@Caleth Dobry haczyk! Wynik kopiowania/wklejania z wielu źródeł. – AOK

+0

Amazing! Dzięki temu mój kod staje się bardziej czytelny. – tom

2

Dzięki if constexpr możemy poprawić rozwiązanie 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); 
     } 
} 

Dzięki temu możemy pozbyć się tego ::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... 
}); 

Niestety trzeba jeszcze napisać i.value.


Należy pamiętać, że nie byłoby to możliwe bez if constexpr ponieważ droga AOK za wymagałoby częściowej specjalizacji szablonu static_for.

Powiązane problemy