2015-04-06 6 views
5

Mam klasę szablonu, która przechowuje tablicę liczb i chcę zastosować istniejące (skalarne) funkcje do każdego elementu. Na przykład, jeśli przyjmiemy, że moja klasa to std :: vector, to chcę mieć możliwość wywoływania (na przykład) funkcji std :: cos na wszystkich elementach.Jak zastosować funkcję do wszystkich elementów w tablicy (w klasie szablonu C++)

Może rozmowa będzie wyglądać następująco:

std::vector<float> A(3, 0.1f); 
std::vector<float> B = vector_function(std::cos, A); 

nb Muszę również obsługiwać std :: complex <> types (dla których wywoływana jest odpowiednia funkcja std :: cos).

znalazłem this answer który radzi typ funkcji jako parametr szablonu:

template<typename T, typename F> 
std::vector<T> vector_function(F func, std::vector<T> x) 

Jednak nie mogę uzyskać to do pracy w ogóle (może dlatego, że funkcje takie jak std :: grzechu i std : cos są zarówno szablonowe, jak i przeciążone?).

Próbowałem również używać std::transform, ale szybko stał się bardzo brzydki. Dla non-typów złożonych, udało mi się go uruchomić za pomocą typedef:

std::vector<float> A(2, -1.23f); 
typedef float (*func_ptr)(float); 
std::transform(A.begin(), A.end(), A.begin(), (func_ptr) std::abs); 

jednak próbuje tej samej sztuczki z std :: kompleks <> typów spowodował zawieszenie run-time.

Czy istnieje dobry sposób, aby to zadziałało? Utknąłem na tym od wieków.

+1

Jak o po prostu 'std :: for_each'? – PaulMcKenzie

+1

Co do początku "brzydkiego", jeśli używasz C++ 11, 'std :: transform' może być napisany przy użyciu lambda bez' typedef': http://ideone.com/stYywt – PaulMcKenzie

+0

Sprawdziłem 'std :: for_each' i wydaje się po prostu wywoływać funkcję dla każdej wartości ... Nie widzę jak zachować wynik (może "std :: transform" jest lepsze pod tym względem?). Nie korzystałem wcześniej z funkcji lambda. Czy istnieje sposób, aby to ogólne/szablonowe? – Harry

Odpowiedz

0

Powinieneś rzucić okiem na ten post Richel Bilderbeek (Math code snippet to make all elements in a container positive), który pokazuje, dlaczego abs nie będzie działać w taki sposób transformacji. Powodem, dla którego nie zadziała, jest struktura funkcji abs (zobacz http://www.cplusplus.com/reference/cstdlib/abs/). Zobaczysz, że abs nie jest szablonem w przeciwieństwie do innych funkcji (głównie funkcji binarnych) znajdujących się w bibliotece functional. Rozwiązanie dostępne na stronie Richel's, aby pokazać, w jaki sposób zastosować abs do powiedzenia, wektor liczb całkowitych.

Teraz, jeśli chcesz zastosować abs do kontenera za pomocą transformacji, powinieneś wiedzieć, że funkcja transformacji otrzymała skomplikowany obiekt i nie będzie wiedział, jak zastosować abs. Najprostszym sposobem rozwiązania tego problemu jest napisanie własnych szablonowych funkcji jednoargumentowych.

Mam przykład poniżej, gdzie stosuję abs do części rzeczywistych i urojonych obiektu złożonego.

#include <iostream> 
#include <vector> 
#include <algorithm> 
#include <complex> 
#include <functional> 

template <typename T> 
std::complex<T> abs(const std::complex<T> &in) { 
    return std::complex<T>(std::abs(in.real()), std::abs(in.imag())); 
}; 

int main() { 
    std::vector<std::complex<int>> v; 
    std::complex<int> c(10,-6); 
    v.push_back(c); 
    std::complex<int> d(-5, 5); 
    v.push_back(d); 

    std::transform(v.begin(), v.end(), v.begin(), abs<int>); 

    //Print out result 
    for (auto it = v.begin(); it != v.end(); ++it) 
    std::cout << *it << std::endl; 

    return 0; 
} 

Jak już wspomniałeś, chciałeś zastosować abs z biblioteki complex. Aby uniknąć nieokreślonego zachowania (patrz this), należy użyć nazwy double dla złożonych obiektów.

+0

Dzięki, przeczytam to. Ale 'abs()' nie jest zdefiniowany w ten sposób dla liczb zespolonych. Dla złożonej liczby z, abs (z) = sqrt (z.real() * z.real() + z.imag() * z.imag()). W takim przypadku typ zwrotu powinien być niezłożony. Implementacja C++ jest opisana w [link] (http://www.cplusplus.com/reference/complex/abs/). – Harry

+0

Tak, to nadal działa, jeśli zmienię funkcję na std :: cos. Działa to również, jeśli zachowuję funkcję jako std :: abs i zmieniam typ wejścia na std :: vector . Jednak nie działa, jeśli zmienię zarówno funkcję, jak i typ. Otrzymuję komunikat 'error: can not convert 'std :: complex ' na 'double' w przypisaniu'. – Harry

+0

Nie rozumiem, zmieniłeś funkcję na std :: cos i używasz std :: vector >? To działa dobrze dla mnie. – Mohammad

4

Nadal uważam, że należy użyć std::transform:

template <class OutputIter, class UnaryFunction> 
void apply_pointwise(OutputIter first, OutputIter last, UnaryFunction f) 
{ 
    std::transform(first, last, first, f); 
} 

Funkcja ta działa nie tylko dla std::vector typów, ale rzeczywiście dowolnego pojemnika, który ma funkcję begin() i end() członka, a nawet pracuje dla C- tablice stylów za pomocą darmowych funkcji std::begin i std::end. Jednolitą funkcją może być dowolna dowolna funkcja, obiekt funktora, wyrażenie lambda, a nawet funkcje składowe klasy.

Jeśli chodzi o problem z std::sin, ta bezpłatna funkcja jest szablonem, a więc kompilator nie może wiedzieć, którego instancji szablonu potrzebujesz.

Jeśli masz dostęp do C++ 11, następnie wystarczy użyć wyrażenia lambda:

std::vector<float> v; 
// ... 
apply_pointwise(v.begin(), v.end(), [](const float f) 
{ 
    return std::sin(f); 
}); 

ten sposób kompilator wie, że powinien on zastąpić T=float jako parametr szablonu.

Jeśli można użyć funkcji C, można również użyć funkcji sinf, która nie jest na matrycy i bierze float jako parametr:

apply_pointwise(v.begin(), v.end(), sinf); 
+0

Właśnie uaktualniłem mój kompilator wczoraj, więc mogę użyć C++ 11. Ta składnia lambda wygląda dość dziwnie, więc zajmie mi trochę czasu, aby dowiedzieć się, co się dzieje ... ale wygląda na to, że muszę ręcznie wpisać szablon typu "v". Co się stanie, jeśli zmieni się typ "v"? Czy mogę uzyskać funkcję automatycznego obliczenia tego? – Harry

+0

@Harry 'Że składnia lambda wygląda dość dziwacznie" Przyzwyczaj się do tego. Jest to główna część C++ 11. Po drugie, zmień typ 'v' na jakie inne typy? Cokolwiek to jest, musi spełniać określone przez ciebie warunki - nie możesz zakładać, że możesz napisać funkcję, w której 'v' może być dowolnym, czym chcesz. – PaulMcKenzie

+0

Myślę, że mam na myśli to, że jeśli 'v' był tylko skalarem, to (niezależnie od jego typu) mogłem go przekazać do' std :: cos', a odpowiednia funkcja zawsze byłaby wywoływana automatycznie. Myślę, że mógłbym osiągnąć to samo dla 'std :: vector's przez przeciążenie każdej funkcji indywidualnie, ale nie używając jednej ogólnej funkcji ... – Harry