2014-06-27 11 views
6

Mam wektor typu zdefiniowanego przez użytkownika (Student). Mam 2 funkcje, które są prawie identyczne, z wyjątkiem pojedynczego wywołania funkcji wewnątrz nich.Przekazywanie algorytmu STL do innej funkcji

Oto funkcje 2:

Student lowest_grade(const std::vector<Student> &all_students){ 
    return *std::min_element(std::begin(all_students), std::end(all_students), 
     [](const Student &a, const Student &b){ 
    return a.get_average() < b.get_average();}); 
} 

Student highest_grade(const std::vector<Student> &all_students){ 
    return *std::max_element(std::begin(all_students), std::end(all_students), 
     [](const Student &a, const Student &b){ 
    return a.get_average() < b.get_average();}); 
} 

Obie te funkcje działają poprawnie dla mojego użytku, ale wydaje się, że to może być łatwo skonstruowany lepiej. Chcę utworzyć funkcję, którą może przejść w obu min_element lub max_element, coś jak:

template <typename func> 
Student dispatch(const std::vector<Student> &all_students, func){ 
    return *func(std::begin(all_students), std::end(all_students), 
     [](const Student &a, const Student &b){ 
    return a.get_average() < b.get_average();}); 
} 

Ale nie uda się uzyskać to, aby działać poprawnie. Nie jestem pewien, jak to zrobić.

EDIT - To jak mam wywołanie funkcji wysyłki + komunikat o błędzie:

std::cout<<"lowest: "<< dispatch(all_students, std::max_element); 

komunikat o błędzie jest:

g++ m.cpp -std=c++11 -Wall -o main 
m.cpp: In function ‘int main()’: 
m.cpp:86:63: error: missing template arguments before ‘(’ token 
    std::cout<<"lowest: "<< dispatch(all_students, std::function(std::max_element)); 
                  ^
[email protected]:~/Desktop/Prog/daily/167m$ make 
g++ m.cpp -std=c++11 -Wall -o main 
m.cpp: In function ‘int main()’: 
m.cpp:86:81: error: no matching function for call to ‘dispatch(std::vector<Student>&, <unresolved overloaded function type>)’ 
    std::cout<<"lowest: "<< dispatch<std::function>(all_students, std::max_element); 
                       ^
m.cpp:86:81: note: candidate is: 
m.cpp:71:9: note: template<class func> Student dispatch(const std::vector<Student>&, func) 
Student dispatch(const std::vector<Student> &all_students, func){ 
     ^
m.cpp:71:9: note: template argument deduction/substitution failed: 
+0

Czy możecie wyjaśnić, jak to nie działa? Szczególnie proszę pokazać, w jaki sposób korzystasz z funkcji "wysyłki". –

+0

Jeśli obliczasz jednocześnie min i max, weź pod uwagę [std :: minmax_element] (http://en.cppreference.com/w/cpp/algorithm/minmax_element). –

Odpowiedz

1

Twoja funkcja może być napisany tak, jak chcesz, co następuje:

template<typename Func> 
Student dispatch(const std::vector<Student> &all_students, Func func) 
{ 
    assert(!all_students.empty()); 
    return *func(std::begin(all_students), std::end(all_students), 
       [](const Student &a, const Student &b){ 
        return a.get_average() < b.get_average();}); 
} 

I wywołany jako

dispatch(students, 
     std::min_element<decltype(students)::const_iterator, 
          bool(*)(const Student&, const Student&)>); 
dispatch(students, 
     std::max_element<decltype(students)::const_iterator, 
          bool(*)(const Student&, const Student&)>); 

Możesz trochę ograniczyć gadatliwość, jeśli zaimplementujesz operator< dla Student. Pozwoli to na pominięcie argumentu szablonu dla komparatora.

template<typename Func> 
Student dispatch(const std::vector<Student> &all_students, Func func) 
{ 
    assert(!all_students.empty()); 
    return *func(std::begin(all_students), std::end(all_students)); 
} 

dispatch(students, 
     std::min_element<decltype(students)::const_iterator>); 
dispatch(students, 
     std::max_element<decltype(students)::const_iterator>); 

Jeszcze innym sposobem, aby to zrobić, jest zawsze zadzwonić min_element zasięgu wysyłki, lecz przechodzą w komparatorów z różnymi zachowaniami.

template<typename Comparator> 
Student dispatch(const std::vector<Student> &all_students, Comparator comp) 
{ 
    assert(!all_students.empty()); 
    return *std::min_element(std::begin(all_students), std::end(all_students), 
          comp); 
} 

dispatch(students, std::less<Student>()); 
dispatch(students, std::greater<Student>()); // requires operator> for Student 

Wreszcie, jeśli zawsze będzie pobierać zarówno najniższą i najwyższą ocen, średnia Biblioteka oferuje std::minmax_element że będzie pobierać zarówno w pojedynczym wywołaniu.

auto minmax = std::minmax_element(std::begin(students), std::end(students)); 

Live demo ze wszystkich różnych opcji.

3

to zrobi:

template <typename func> 
Student dispatch(const std::vector<Student> &all_students, const func& fn){ 
    return *fn(std::begin(all_students), std::end(all_students), 
     [](const Student &a, const Student &b){ 
    return a.get_average() < b.get_average();}); 
} 

Parametr szablonu jest tylko typem czegoś.

Proponuję ostrożnie wywoływać tę metodę nigdy przy pustym wektorze, ponieważ spowoduje to zgłoszenie wyjątku podczas usuwania pustego iteratora. Lepiej byłoby:

template <typename func> 
Student dispatch(const std::vector<Student> &all_students, const func& fn){ 
    auto it = fn(std::begin(all_students), std::end(all_students), 
     [](const Student &a, const Student &b){ 
    return a.get_average() < b.get_average();}); 
    if (it != all_students.end()) { 
    return *it; 
    } 
    // Some exception handling, because returning an instance of student is not possible. 
} 

Inną propozycją jest posortowanie uczniów przed użyciem danych. Wtedy będziesz mógł również uzyskać inne dane statystyczne, takie jak mediana.

std::sort(all_students.begin(), all_students.end() [](const Student &a, const Student &b){return a.get_average() < b.get_average();}); 

Najniższy uczeń jest pierwszym elementem, a najwyższy ostatnim. Zapobiegnie to także zgłaszaniu wyjątków.

Jest inny problem z połączeniem. Trzeba zadzwonić wysyłkowy jak:

dispatch(all_students, std::max_element<std::vector<Student>::const_iterator, std::function<bool(const Student &, const Student &)>>); 

STL ma żadnej magii i odliczenia nie może decydować o sobie, które max_element funkcja twój chce. Więc musisz to określić.

+0

Powinienem być wyraźniejszy, zdaję sobie sprawę, że ten rodzaj jest prawdopodobnie najlepszą opcją, jednak jest to tylko mały program demonstracyjny do pracy nad uczeniem C11. –

2

std::max_element to funkcja szablonu, a kompilator nie może wywnioskować typu szablonu, który jest potrzebny w ten sposób.

Możesz użyć następujących zmusić której prototyp chcesz:

// Your lambda as functor 
struct CompAverage 
{ 
    bool operator() (const Student & a, const Student & b) const 
    { 
     return a.get_average() < b.get_average(); 
    } 
}; 

using Student_IT = std::vector<Student>::const_iterator; 

Student dispatch(const std::vector<Student> &all_students, 
       Student_IT (*f)(Student_IT, Student_IT, CompAverage)) 
{ 
    return *f(std::begin(all_students), std::end(all_students), CompAverage{}); 
} 

int main() 
{ 
    std::vector<Student> v(42); 

    dispatch(v, &std::min_element); 
    dispatch(v, &std::max_element); 
    return 0; 
} 

Live example

2

Mój preferowany sposób, aby to zrobić, to owinąć algorytm w lambdę, a następnie przekazać lambdę do funkcji szablonu. Ma ładną składnię, gdy owijacie ją w makro:

#define LIFT(...)             \ 
    ([](auto&&... args) -> decltype(auto) {      \ 
     return __VA_ARGS__(std::forward<decltype(args)>(args)...); \ 
    }) 

template <typename Func> 
Student dispatch(const std::vector<Student> &all_students, Func func){ 
    return *func(std::begin(all_students), std::end(all_students), 
     [](const Student &a, const Student &b){ 
    return a.get_average() < b.get_average();}); 
} 

// ... 

std::cout<<"lowest: "<< dispatch(all_students, LIFT(std::max_element)); 
+0

Chociaż nie jestem fanem definiowania, jest to fajna magia :) –

+0

To wygląda niesamowicie znajomo. Za pomocą [odpowiedniego mechanizmu] (https://github.com/sjolsen/sjo/blob/master/lift.hh) można również użyć polecenia 'ordered_by (MFLIFT (get_average))) zamiast wartości lambda. –

Powiązane problemy