2013-06-10 9 views
7

Mam funkcję, która skanuje system plików użytkownika, wypełnia wektor ścieżkami, a następnie sortuje je lub nie. Ponieważ użytkownik powinien móc decydować podczas kompilacji, czy chce sortować wektor, czy nie, używam szablonów i klas pomocniczych w miejsce bardzo pożądanego (ale nieistniejącego) "statycznego jeśli".Metaprogramowanie szablonu C++, obejście "statyczne, jeśli" - czy można je poprawić?

Rozważmy następujący kod:

enum class Sort{Alphabetic, Unsorted}; 

template<Sort TS> struct SortHelper; 
template<> struct SortHelper<Sort::Alphabetic> 
{ 
    static void sort(vector<string>& mTarget) { sort(begin(mTarget), end(mTarget)); } 
}; 
template<> struct SortHelper<Sort::Unsorted> 
{ 
    static void sort(vector<string>&) { } 
}; 

template<Sort TS> struct DoSomethingHelper 
{ 
    static void(vector<string>& mTarget) 
    { 
     // do something with mTarget 
     SortHelper<TS>::sort(mTarget); 
    } 
}; 

Kod pisałem powyżej jest znacznie uproszczone od the original, który zajmuje wiele parametrów szablonu, aby umożliwić użytkownikowi dostosowanie jeszcze wyników funkcji w czasie kompilacji .

Czy istnieje alternatywa dla używania wszystkich tych klas pomocników? Robi się naprawdę brudny i trudny do odczytania.

Idealnie, to jest to, co chciałbym napisać:

enum class Sort{Alphabetic, Unsorted}; 
template<Sort TS> struct DoSomethingHelper 
{ 
    static void(vector<string>& mTarget) 
    { 
     // do something with mTarget 
     static_if(TS == Sort::Unsorted) { /* do nothing */ } 
     static_if(TS == Sort::Alphabetic) { sort(begin(mTarget), end(mTarget)); } 
    } 
}; 
+1

Nieistniejące i [prawdopodobnie nieistniejące] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3613.pdf). – Morwenn

+3

Hm, dlaczego dbasz o to podczas kompilacji? Wygląda na to, że bardzo nad tym zastanawiasz. Jeśli najpierw skanujesz drzewo katalogów, pojedyncza gałąź czasu wykonywania będzie znacznie niższa od testu porównawczego w zakresie wpływu na wydajność. –

+2

Jeśli wszystkie gałęzie są syntaktycznie i semantycznie poprawne, możesz użyć zwykłego 'if', a optymalizator wyeliminuje nieaktywne gałęzie, a także niepotrzebne comditionals/jumps. Jeśli nieaktywne gałęzie nie kompilują się, pomocnicy są jedyną drogą. – Angew

Odpowiedz

17

Ponieważ wartość jest znany w czasie kompilacji (parametr typu non-template) można doskonale napisać „normalne” if:

template<Sort TS> 
void someFunction(vector<string>& mTarget) 
{ 
    if (TS == Sort::Alphabetic) { sort(begin(mTarget), end(mTarget)); } 
    // else if (TS == Sort::Unsorted) {} 
} 

kompilator wykona stały składany i martwy eliminacja kod (jeśli takie optymalizacje są włączone, oczywiście), a wynik będzie e tak samo jak w przypadku hipotetycznego static_if.

+1

Wygląda fantastycznie - czy istnieje szczegółowa dokumentacja na temat tego rodzaju optymalizacji? Chciałbym wiedzieć, kiedy jest w 100% gwarantowane, lub gdy kompilator ma problem z tym, etc ... –

+0

gcc ma [tę stronę] (http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options. html), który wyjaśnia każdą opcję optymalizacji, którą możesz kontrolować, włącz -fdce, eliminacja martwego kodu – SirGuy

+2

Nie jest to w ogóle gwarantowane przez standard, ale jest dozwolone (przez klauzulę as-if: jeśli nie możesz osiągnąć martwego kodu, to jest taki sam jak-jeśli go tam nie ma), ale na żadnym nietrywialnym poziomie optymalizacji, nigdy nie widziałem ani jednego kompilatora C++ * nie * zrób to. Aby uzyskać przenośność, uczyń to proste porównanie z oczywistą wartością podczas kompilacji. Zauważ, że kod w oddziale musi być legalny do kompilacji, jeśli nie, warunek jest prawdziwy. – Yakk

11

Obawiam się, że doszło do nieporozumienia na temat korzystania z static_if.

Z pewnością możesz użyć static_if (lub jakiejkolwiek sztuczki, którą naprawdę chcesz), aby spróbować uzyskać optymalizację, ale to nie jest jej pierwszy cel.

Pierwszy cel static_if to semantyczny. Pozwól mi zademonstrować to z std::advance. Typowa implementacja std::advance użyje przełącznika typu do wyboru, w czasie kompilacji, między O (1) realizacja (Random Access iteratory) oraz O (n) wdrożenia (dla innych):

template <typename It, typename D> 
void advance_impl(It& it, D d, random_access_iterator_tag) 
{ 
    it += d; 
} 

template <typename It, typename D> 
void advance_impl(It& it, D d, bidirectional_iterator_tag) 
{ 
    if (d > D(0)) { for (D i(0); i < d; ++i) { ++it; } } 
    else   { for (D i(0); i > d; --i) { --it; } } 
} 

template <typename It, typename D> 
void advance_impl(It& it, D d, input_iterator_tag) 
{ 
    for (D i(0); i < d; ++i) { ++it; } 
} 

i wreszcie:

template <typename It, typename D> 
void advance(It& it, D d) 
{ 
    typename std::iterator_traits<It>::iterator_category c; 
    advance_impl(it, d, c); 
} 

Dlaczego nie używać tylko if w tym przypadku? Ponieważ nie skompilowałoby się.

  • dwukierunkowym Iterator nie obsługuje +=
  • wejście Iterator (lub Forward Iterator) nie obsługuje --

Zatem tylko sposób zaimplementować funkcjonalność jest statycznie wysyłanie do funkcji tylko za pomocą dostępnych operacji na danym typie.

0

Co ze specjalnością szablonów?

#include <vector> 
#include <iostream> 
#include <algorithm> 

using namespace std; 

enum class Sort { 
    Alphabetic, 
    Unsorted 
}; 

template<Sort TS> struct DoSomethingHelper { 
    static void someFunction(vector<string>& mTarget) 
    {} 
}; 

template<> struct DoSomethingHelper<Sort::Unsorted> { 
    static void someFunction(vector<string>& mTarget) { 

    } 
}; 

template<> struct DoSomethingHelper<Sort::Alphabetic> { 
    static void someFunction(vector<string>& mTarget) { 
     sort(begin(mTarget), end(mTarget)); 
    } 
}; 

int main() { 
    vector<string> v = {{"foo", "bar", "foo2", "superman", ".."}}; 

    DoSomethingHelper<Sort::Alphabetic> helper; 
    helper.someFunction(v); 

    for (string& s : v) { 
     cout << s << endl; 
    } 
    return 0; 
} 

Edit: jestem idiotą.

Powiązane problemy