5

Jestem programistą Scala/Java, który chce ponownie wprowadzić się do C++ i nauczyć się niektórych ekscytujących funkcji w C++ 0x. Chciałem zacząć od zaprojektowania mojej własnej, nieco funkcjonalnej biblioteki kolekcji, opartej na kolekcjach Scali, dzięki czemu mogłem uzyskać solidne zrozumienie szablonów. Problem, na który napotykam, polega na tym, że kompilator nie jest w stanie wywnioskować żadnych informacji o typach dla szablonowych obiektów funkcyjnych.C++ 0x obiektowe wnioskowanie obiektowe obiektu

FC++ wydaje się, że rozwiązało to za pomocą "Podpisy". Te wydają się bardzo podobne do typ_typu result_type i pomyślałem, że dostanę to używając nowej składni funkcji. Czy ktokolwiek może zaproponować sposób zrobienia tego w C++ 0x, jeśli to możliwe, lub przynajmniej wyjaśnić, w jaki sposób FC++ był w stanie to zrobić? Oto fragment kodu Grałem około z

#include <vector> 
#include <iostream> 
#include <algorithm> 
using namespace std; 

template<class T> 
class ArrayBuffer { 
private: 
    vector<T> array; 
public: 
    ArrayBuffer(); 
    ArrayBuffer(vector<T> a) : array(a) {} 

    template<typename Fn> 
    void foreach(Fn fn) { 
     for(unsigned int i = 0; i < array.size(); i++) fn(array[i]); 
    } 

    template<typename Fn> 
    auto map(Fn fn) -> ArrayBuffer<decltype(fn(T()))> { 
     vector<decltype(fn(T()))> result(array.size()); 
     for(int unsigned i = 0; i < array.size(); i++) result[i] = fn(array[i]); 
     return result; 
    } 
}; 

template<typename T> 
class Print { 
    public: 
    void operator()(T elem) { cout<<elem<<endl; } 
}; 

template<typename T> 
class Square{ 
public: 
    auto operator()(T elem) -> T { 
     return elem * elem; 
    } 
}; 

int main() { 
    vector<int> some_list = {5, 3, 1, 2, 4}; 
    ArrayBuffer<int> iterable(some_list); 
    ArrayBuffer<int> squared = iterable.map(Square<int>()); // works as expected 
    iterable.foreach(Print<int>()); // Prints 25 9 1 4 16 as expected 
    iterable.foreach(Print()); // Is there a way or syntax for the compiler to infer that the template must be an int? 
    ArrayBuffer<int> squared2 = iterable.map(Square()); // Same as above - compiler should be able to infer the template. 
} 

Odpowiedz

5

Można dokonać operator() szablon zbyt

class Print { 
    public: 
    template<typename T> 
    void operator()(T elem) { cout<<elem<<endl; } 
}; 

Następnie można przejść Print(). Do przekazywania argumentów jak w ArrayBuffer<decltype(fn(T()))> Polecam używanie declval, więc można również pracować non-default constructible T

ArrayBuffer<decltype(fn(declval<T>()))> 
+0

Dziękujemy! To działa idealnie. Nie pomyślałem o przeniesieniu szablonu do operatora. Doceniam również wskazówkę dotyczącą deklwalacji. Mam krótką obserwację - czy istnieje sposób, aby to zrobić za pomocą wskaźnika funkcji na szablonie? Na przykład, jeśli mamy funkcję drukowania szablonu, mogę nazwać iterable.foreach (& print ); Uważam, że odpowiedź brzmi nie. –

0

Czy istnieje sposób lub składnia kompilator wywnioskować, że szablon musi być int ?

Problemem jest szablon nie trzeba być int.
To jest równie ważne.

iterable.foreach(Print<float>()); 

To, o co naprawdę prosisz, to.
Czy mogę utworzyć szablonowy funktor, który ma inną wartość domyślną, w zależności od kontekstu, w którym jest używany?

Odpowiedź brzmi: nie. Sądzę, że teoretycznie moglibyśmy to zrobić w prostych przypadkach. Jednak przypadki narożne szybko sprawiają, że jest to niemożliwe. Prawdziwym kontekstem tej funkcji jest kompletna jednostka kompilacji, a nie tylko linia, w której jest tworzona.

1

Wygląda na to, że wymyślasz nową bibliotekę standardową C++. Nie mówię o twoich pojemnikach.
C++ jest już całkiem funkcjonalnie wyposażony.

Myślę, że brakuje ci kilku kluczowych punktów dotyczących standardowej biblioteki C++.

  • jest Typowa przede obiektowe sekundę.
    Jeśli algrithm można zaimplementować w sposób ogólny, nie zostanie on dołączony do klasy.
    ArrayBuffer::foreach == std::for_each
    ArrayBuffer::map == std::transform
  • Standardowe algorytmy pracy na iteratory zamiast pełnych pojemników. Często brakuje tego nowym programistom C++, ponieważ zarówno w Javie, jak iw C# brakuje koncepcji. Iteratory są bardziej ekspresyjne/elastyczne niż same pojemniki. Iteratory to arguably do zrobienia.To powiedziawszy, Ranges są znacznie bardziej zwięzły sposób na wyrażanie iteratorów (zakres to tylko parowane iteratory).

Oto przykład użycia C++ funkcjonalnie. Jest to również dobry przykład, dlaczego C# nie używało iteratorów. Mimo że są bardzo potężni, ich gadatliwość zastrasza grupę docelową C#. Java nie używa iteratorów, ponieważ nie są one zorientowane w projektowaniu języka na początku.

struct Print 
{ 
    template<typename T> 
    void operator()(const T& t) 
    { std::cout << t << std::endl; } 
}; 

struct Squared 
{ 
    template<typename T> 
    T operator()(const T& t) 
    { return t*t; } 
}; 

int main() 
{ 
    std::vector<int> vi; 
    std::foreach(vi.begin(), vi.end(), Print()); 
    std::foreach(vi.begin(), vi.end(), [](int i){ std::cout<<i<<std::endl; }); 

    std::vector<int> vi_squared; 

    std::transform(vi.begin(), vi.end(), std::back_inserter(vi_squared), Squared()); 
    // or 
    vi_squared.resize(vi.size()); 
    std::transform(vi.begin(), vi.end(), vi_squared.begin(), Squared()); 
} 
+0

Dzięki za porady. Rozumiem, że STL oddziela niektóre algorytmy od kontenerów w przyjemny sposób. Nadal pozostawia wiele rzeczy, jak fałdy, redukcje, widoki, plasterki, spłaszczenia, płaskie mapy, itp. Wreszcie, przekształcenie w C++ jest destrukcyjne. Chciałbym zaimplementować w pełni trwałe kolekcje oprócz zbiorów zmiennych, a nie można tego zrobić przy podzielonym projekcie STL. Mapa powinna być operacją na kontenerze, a kontener powinien mieć możliwość wyboru sposobu jej wdrożenia. –

+0

Właściwie to cofam. Transformacja nie wydaje się być destrukcyjna. Mimo to uważam, że posiadanie operacji w ramach kontenerów ma sens i pozwala każdemu kontenerowi implementować własne specyficzne zachowanie. Nie jestem pewien, ile powtórzeń kodu będę mógł wydostać się z tego projektu, ponieważ moje algorytmy będą mieszane z iteracją. Mam również nadzieję, że użyję CRTP do zjednoczenia różnych kolekcji, które implementuję w ramach jednej hierarchii, dzięki czemu mogę przedstawić interfejs, a nie implementację z biblioteki (metody szablonów nie mogą być wirtualne). Zakresy wyglądają świetnie - doceniam link. –

+0

Możesz nadal specjalizować zachowanie kontenerów, gdy jest on używany w 'std :: transform' lub dowolnym algorytmie standardowym. Zamiast podklasy i przesłonięcia metody mapy, wyspecjalizowałeś się (technicznie częściowo specjalizacja) w metodzie 'std :: transform' dla swojego kontenera. Zwykle wystarczy zdefiniować, w jaki sposób twoja kolekcja jest wystarczająca i iteratory robią to naprawdę dobrze (a zakresy/widoki robią to lepiej). To wydaje mi się właściwe. W 99% algorytm mapy nie dba o semantykę pamięci. Ale jeśli naprawdę potrzebujesz się tam dostać i robić szalone rzeczy, możesz specjalizować się. –