5

Sto cercando di utilizzare fancy -std = C++ 14 caratteristiche per implementare il combinatore "mappa" che vedi nei linguaggi funzionali (da non confondere con std :: map). Il mio obiettivo finale è scrivere un'intestazione "pattern di facciata" per la programmazione funzionale che mi permetta di dimenticare gli effetti collaterali e gli iteratori la maggior parte del tempo. Ho trovato un post da una persona con la stessa mentalità allo https://gist.github.com/phatak-dev/766eccf8c72484ad623b. La versione di Madhukara della mappa sembraCombinatore di mappe C++ funzionale con auto

template <typename Collection,typename unop> 
    Collection map(Collection col,unop op) { 
    std::transform(col.begin(),col.end(),col.begin(),op); 
    return col; 
} 

Sembra funzionare perfettamente fino a quando non chiedere qualcosa di stupido, ma il tipo di ritorno deve essere la stessa della raccolta di input. Il mio tentativo di generalizzare ad avere dominio e la gamma di tipi diversi è il seguente:

template <typename Collection, typename function> 
auto map(function f, Collection c) { 
    auto result; 
    std::transform(c.begin(),c.end(),result.begin(),f); 
    return result; 
} 

Questo non si compila, ma si spera che sia chiaro a qualcuno quello che sto cercando di fare ... Voglio inizializzare un vuoto stesso tipo-di-contenitore-come-c di output-type-of-f's, quindi inserire i f(c[i]) s in esso. Il compilatore si lamenta che la dichiarazione di 'risultato automatico' non ha inizializzatore, ma non so come chiedere un vuoto qualunque dei whatevers. C'è un modo per modificare quella linea per farlo fare ciò che sto cercando di fare? Non ho mai provato a fare qualcosa di così esotico con l'auto prima, quindi qualsiasi suggerimento aggiuntivo è benvenuto.

Grazie!

John

EDIT: Ecco un esempio di utilizzo si spera-sensical:

auto first_letter = [](string s) { return s[0]; } 
vector<string> words; 
words.push_back("hello"); words.push_back("world"); 
vector<char> first_letters = map(first_letter, words); // {'h','w'} 

Edit 2: Ecco un altro approccio che utilizza uno dei pesi massimi "flussi" biblioteca (da non confondere con IO flussi) al l'attuazione del "modello iteratore", come flussi di Java:

http://jscheiny.github.io/Streams/

l'approccio Java: http://tutorials.jenkov.com/java-collections/streams.html

Questo approccio ai flussi consente una maggiore libertà di scelta sui tipi di contenitori (come diversi rispondenti sembrano essere a favore) e una valutazione lenta.

+0

Vorresti un uso di questo per assomigliare? Come può un chiamante specificare se desidera che sia restituito un 'list' o' vector', per esempio? –

+0

Mi spiace di non aver capito bene e me ne sono reso conto immediatamente, ma mi hai chiesto prima di editare. Mi piacerebbe che il contenitore di output fosse lo stesso del contenitore di input, ma non necessariamente gli stessi tipi di contenuto. –

+0

E come si popolerebbe 'result' se inizia vuoto e non si conosce l'interfaccia ad esso? Come sapresti chiamare 'push_back',' insert', o qualunque sia la funzione? – PaulMcKenzie

risposta

2

Basta usare boost::adaptors::tranformed:

#include <boost/range/adaptor/transformed.hpp> 

template <typename Collection, typename function> 
auto map(function f, Collection c) { 
    return c | boost::adaptors::transformed(f); 
} 

Con questa gamma - è possibile creare qualsiasi contenitore che si desidera.

char first_letter(string s) { return s[0]; } 
vector<string> words; 
words.push_back("hello"); words.push_back("world"); 
auto transformed_range = map(first_letter, words); 
vector<char> first_letters(begin(transformed_range), end(transformed_range)); 

Se si insiste per avere map funzione che restituisce il contenitore, non vanno - aggiungere più un parametro a questo modello di funzione:

#include <boost/range/adaptor/transformed.hpp> 

template <typename Result, typename Collection, typename function> 
auto map(function f, Collection c) { 
    auto transformed_range = c | boost::adaptors::transformed(f); 
    return Result(begin(transformed_range), end(transformed_range)); 
} 

char first_letter(string s) { return s[0]; } 
vector<string> words; 
words.push_back("hello"); words.push_back("world"); 
vector<char> first_letters = map<vector<char>>(first_letter, words); 

Ma se davvero insistere per avere l'esatto comportamento come vuoi: devi avere alcuni tratti che sanno come convertire il tipo di raccolta in un altro tipo di raccolta con valore trasformato.

In primo luogo - il modo per avere new_value_type:

template <typename Function, typename OldValueType> 
struct MapToTransformedValue 
{ 
    using type = decltype(std::declval<Function>()(std::declval<OldValueType>())); 
}; 

Il tratto generale:

template <typename Function, typename Container> 
struct MapToTransformedContainer; 

Il caso più semplice - per std::array:

// for std::array 
template <typename Function, typename OldValueType, std::size_t N> 
struct MapToTransformedContainer<Function, std::array<OldValueType, N>> 
{ 
    using value_type = typename MapToTransformedValue<Function, OldValueType>::type; 
    using type = std::array<value_type, N>; 
}; 

Per std::vector - un po 'di più complicato - è necessario fornire un nuovo allocatore, per lo standard catori - è possibile utilizzare il relativo modello rebind:

// for std::vector 
template <typename Function, typename OldValueType, typename OldAllocator> 
struct MapToTransformedContainer<Function, std::vector<OldValueType, OldAllocator>> 
{ 
    using value_type = typename MapToTransformedValue<Function, OldValueType>::type; 
    using allocator = typename OldAllocator::template rebind<value_type>::other; 
    using type = std::vector<value_type, allocator>; 
}; 

Quindi la funzione sarà simile al seguente:

template <typename Collection, typename function> 
auto map(function f, Collection c) 
{ 
    using NewCollectionType = typename MapToTransformedContainer<function, Collection>::type; 
    auto transformed_range = c | boost::adaptors::transformed(f); 
    return NewCollectionType (begin(transformed_range), end(transformed_range)); 
} 

ora - il vostro main() corrisponde a quello desiderato:

char first_letter(std::string const& s) { return s[0]; } 

int main() { 
    std::vector<std::string> words; 
    words.push_back("hello"); words.push_back("world"); 
    auto first_letters = map(first_letter, words); 
    std::cout << first_letters[0] << std::endl; 
} 

Attenzione che per altri contenitori dove value_type consiste di Key,Value coppia - come std::map, std::set (e il loro disordine Ed _... fratelli) è necessario definire un altro specializzazione delle MapToTransformedContainer ...

+0

@ flagrant2 - così è - il comportamento desiderato ... – PiotrNycz

+0

Penso che cambiare solo 'value_type' e copiare-incollare' OtherTypes' (insieme a allocatori, comparatori e whatnot) non sia l'idea migliore (se si compila qualunque cosa) –

+0

@PiotrSkotnicki - corretto, grazie per averlo indicato – PiotrNycz

0

Solo per il gusto di farlo, ecco il mio andare a questo problema:

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

template <template<typename...> class Collection> 
struct mapper { 
    template<typename function, typename T, typename... Rest> 
    static auto map(function f, const Collection<T, Rest...>& c) { 
     Collection<decltype(f(*c.begin()))> result; 
     std::transform(c.begin(),c.end(),std::inserter(result, result.end()),f); 
     return result; 
    } 
}; 

int main() 
{ 
    // Example 1 
    std::vector<int> v{0, 1}; 
    auto fv = mapper<std::vector>::map([](const auto& val) { return val + 0.1f; }, v); 
    for (const auto& f : fv) { std::cout << f << " "; } 

    std::cout << "\n"; 

    // Example 2 
    std::set<float> m{1, 2, 3, 4}; 
    auto fm = mapper<std::set>::map([](const auto& val) { return static_cast<int>(val/2.0f); }, m); 
    for (const auto& f : fm) { std::cout << f << " "; } 
} 

Nota, che questo sarebbe solo lavorare più a lungo come si è soddisfatti dei valori predefiniti per tutto tranne il parametro type del contenitore di output.

0

Vorrei fare uso del fatto che la maggior parte dei contenitori ha un costruttore che accetta una coppia di iteratori.

#include <boost/iterator/transform_iterator.hpp> 

template <typename Function, typename Collection> 
struct map_to 
{ 
    Function f; 
    const Collection& c; 

    template <typename T> 
    operator T() && 
    { 
     using std::begin; using std::end; 
     return { boost::make_transform_iterator(begin(c), f) 
       , boost::make_transform_iterator(end(c), f) }; 
    } 
}; 

template <typename Function, typename Collection> 
map_to<Function, Collection> map(Function f, const Collection& c) 
{ 
    return { f, c }; 
} 

Test:

int main() 
{ 
    std::vector<std::string> words; 

    words.push_back("hello"); 
    words.push_back("world"); 

    auto first_letter = [](std::string s) { return s[0]; }; 

    std::vector<char> v = map(first_letter, words); 
    std::set<char> s = map(first_letter, words); 
    std::forward_list<char> f = map(first_letter, words); 
} 

DEMO

+0

Ho provato qualcosa di simile e ho ottenuto errori di compilazione quando ho tentato di usare un'espressione lambda come primo argomento per mappare ...? –

+0

@ flagrant2 Non posso dire cosa potrebbe andare storto senza vedere il tuo codice –

+0

Stavo cercando di utilizzare la parola chiave auto per l'output della mappa, ad es. –