2015-04-06 5 views
5

Ho una classe template che memorizza una serie di numeri e voglio applicare le funzioni esistenti (scalari) a ogni elemento. Per esempio, se assumiamo che la mia classe sia std :: vector, allora voglio essere in grado di chiamare (per esempio) la funzione std :: cos su tutti gli elementi.Come applicare la funzione a tutti gli elementi dell'array (in classe template C++)

Forse una chiamata sarebbe simile a questa:

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

N.B. Devo anche gestire i tipi std :: complex <> (per i quali viene chiamata la funzione std :: cos del complesso appropriato).

ho trovato this answer che suggerisce di prendere il tipo di funzione come parametro di template:

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

Tuttavia, non ho potuto ottenere questo lavoro a tutti (forse perché funzioni come std :: peccato e std: : cos sono entrambi modelli e sovraccarico?).

Ho anche provato a utilizzare std::transform, ma questo è diventato rapidamente molto brutto. Per i tipi non complessi, sono riuscito a farlo funzionare con un 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); 

Tuttavia, tentando lo stesso trucco con <> tipi std :: complesso causato un crash di run-time.

C'è un modo carino per farlo funzionare? Sono stato bloccato su questo per anni.

+1

ne dite semplicemente 'std :: for_each'? – PaulMcKenzie

+1

Per iniziare "brutto", se stai usando C++ 11, 'std :: transform' può essere scritto usando un lambda senza' typedef': http://ideone.com/stYywt – PaulMcKenzie

+0

Ho cercato 'std :: for_each' e sembra chiamare semplicemente una funzione su ogni valore ... Non riesco a vedere come mantenere il risultato (forse 'std :: transform' è preferibile a questo riguardo?). Non ho mai usato le funzioni lambda prima. C'è un modo per rendere questo generico/modello? – Harry

risposta

0

Dovresti dare un'occhiata a questo post di Richel Bilderbeek (Math code snippet to make all elements in a container positive) che ti mostra perché abs non funzionerà in una trasformazione del genere. Il motivo per cui non funziona è dovuto alla struttura della funzione abs (vedere http://www.cplusplus.com/reference/cstdlib/abs/). Vedrete che abs non è un modello a se stesso diverso da altre funzioni (principalmente funzioni binarie) trovate nella libreria functional. Una soluzione disponibile sul sito web di Richel per mostrarti come applicare abs per dire, un vettore di numeri interi.

Ora se si desidera applicare abs a un contenitore utilizzando la trasformazione, è necessario sapere che la funzione di trasformazione ha ricevuto un oggetto complesso e non saprà come applicare abs ad esso. Il modo più semplice per risolverlo è scrivere semplicemente le proprie funzioni unario basate su modelli.

Ho un esempio qui sotto dove applico abs alle parti reali e immaginarie dell'oggetto complesso.

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

Come lei ha detto, si voleva fare applicare l'abs dalla libreria complex. Per evitare il comportamento non specificato (vedere this) si utilizzerà il nome di tipo double per gli oggetti complessi.

+0

Grazie, lo leggerò. Ma 'abs()' non è definito in questo modo per numeri complessi. Per un numero complesso z, abs (z) = sqrt (z.real() * z.real() + z.imag() * z.imag()). In questo caso, il tipo restituito dovrebbe essere non complesso. L'implementazione di C++ è descritta in [link] (http://www.cplusplus.com/reference/complex/abs/). – Harry

+0

Quindi, funziona ancora se cambio la funzione in std :: cos. Funziona anche se tengo la funzione come std :: abs e cambio il tipo di input in std :: vector . Tuttavia, non funziona se cambio sia la funzione che il tipo. Ricevo 'errore: impossibile convertire 'std :: complex ' in 'double' in assignment'. – Harry

+0

Non capisco, hai cambiato funzione in std :: cos e stai usando std :: vector >? Questo funziona bene per me. – Mohammad

4

penso ancora che si dovrebbe usare std::transform:

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

Questa funzione non solo per std::vector tipi, ma anzi ogni contenitore che ha una funzione begin() e end() membro, e funziona anche per il C- array di stile con l'aiuto delle funzioni gratuite std::begin e std::end. La funzione unaria può essere qualsiasi funzione libera, un oggetto functor, un'espressione lambda o anche funzioni membro di una classe.

Per quanto riguarda il problema con std::sin, questa funzione gratuita è basata su modelli e quindi il compilatore non può sapere quale istanza del modello è necessaria.

Se si ha accesso a C++ 11, quindi è sufficiente utilizzare un'espressione lambda:

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

In questo modo, il compilatore sa che dovrebbe sostituire T=float come parametro del modello.

Se è possibile utilizzare le funzioni C, è anche possibile utilizzare la funzione sinf, che non è basato su modelli e prende un float come parametro:

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

Ho appena aggiornato il mio compilatore ieri, quindi posso usare C++ 11. Quella sintassi lambda sembra piuttosto strana, quindi mi ci vorrà un po 'per capire cosa sta succedendo ... ma sembra che debba scrivere il tipo di template di 'v' manualmente. Cosa succede se il tipo di 'v' cambia? Posso ottenere la funzione per capirlo automaticamente? – Harry

+0

@Harry 'Quella sintassi lambda sembra piuttosto strana. Ci si abitua. È una parte importante di C++ 11. In secondo luogo, cambia il tipo di 'v' in quale altro tipo? Qualunque cosa sia, deve soddisfare le condizioni che hai impostato - non puoi assumere che tu possa scrivere una funzione dove 'v' può essere qualsiasi cosa tu voglia che sia. – PaulMcKenzie

+0

Penso che quello che intendo sia che, se 'v' fosse solo uno scalare, allora potrei passarlo a' std :: cos' e (indipendentemente dal suo tipo) e la funzione appropriata verrebbe sempre chiamata automaticamente. Immagino che potrei ottenere lo stesso risultato per 'std :: vector's sovraccaricando ogni funzione individualmente, ma non usando una funzione generica ... – Harry