2012-12-25 30 views
9

Mamprzełożenie std :: krotki w opakowaniu parametru szablonu

typedef std::tuple<A, B> TupleType; 

i chcieliby skorzystać z listy klas dla „szablon”.

Załóżmy, że mam:

template<typename... args> 
std::tuple<args...> parse(std::istream &stream) { 
    return std::make_tuple(args(stream)...); 
} 

i że mogę z powodzeniem używać go z:

auto my_tuple = parse<A, B>(ifs); 

jest to możliwe, aby uniknąć konieczności określić listę klasy A, B, jeśli mam już

typedef std::tuple<A,B> TupleType; 

gdzie lista A, B już jest obecna?

przykład:

#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE 
#include <iostream> // std::cerr 
#include <fstream> // std::ifstream 
#include <tuple> // std::tuple 

class A { 
public: 
    A(std::istream &); // May throw FooBaarException 
}; 

class B { 
public: 
    B(std::istream &); // May throw FooBaarException 
}; 

template<typename... args> 
std::tuple<args...> parse(std::istream &stream) { 
    return std::make_tuple(args(stream)...); 
} 

int main() { 
    std::ifstream ifs; 
    ifs.exceptions(ifstream::eofbit | ifstream::failbit | ifstream::badbit); 
    int res = EXIT_FAILURE; 
    try { 
    ifs.open("/some/file/path", std::ios::in | std::ios::binary); 
    auto my_tuple = parse<A, B>(ifs); // my_tuple is of the type std::tuple<A,B> 
    /* Here do something interesting with my_tuple */ 
    res = EXIT_SUCCESS; 
    } catch (ifstream::failure e) { 
    std::cerr << "error: opening or reading file failed\n"; 
    } catch (FooBaarException e) { 
    std::cerr << "error: parsing in a constructor failed\n"; 
    } 
    return res; 
} 
+1

Wygląda na to, że w konstruktorze chcesz odczytać ciągi. Należy pamiętać, że kolejność wywoływania konstruktorów nie jest określona dla implementacji 'parsowania'. –

+0

@ JohannesSchaub-Litb, ciekawy punkt. Można to zrobić również: http://liveworkspace.org/code/MTk2Nj$0 pod warunkiem, że typy komponentów są różne (możliwe, ale zbyt długie, aby pokazać jako przykład z duplikowanymi typami). – rici

Odpowiedz

5

Podstawowym problemem w tej sytuacji wydaje się, że chcesz się specjalizować szablon funkcji parse do szczególnego przypadku, gdy argument szablonu jest std::tuple. Niestety tego rodzaju specjalizacja nie jest możliwa z szablonami funkcji.

Jednak jest to możliwe w szablonach klas.

Tak, jako pierwszy krok, można zdefiniować parse jako statyczny funkcji w struct, tak:

using std::istream; 
using std::tuple; 
using std::make_tuple; 

struct A { A(const istream &) {} }; 
struct B { B(const istream &) {} }; 

template <typename... Args> 
struct parser 
{ 
    /* Your original function, now inside a struct. 
    I'm using direct tuple construction and an 
    initializer list to circumvent the order-of- 
    construction problem mentioned in the comment 
    to your question. */ 
    static tuple<Args...> parse(const istream &strm) 
    { return tuple<Args...> {Args(strm)...}; } 
}; 

template <typename... Args> 
struct parser<tuple<Args...>> 
{ 
    /* Specialized for tuple. */ 
    static tuple<Args...> parse(const istream &strm) 
    { return parser<Args...>::parse(strm); } 
}; 

Można połączyć je w pożądany sposób:

int main() 
{ 
    typedef tuple<A,B> tuple_type; 
    auto tup = parser<tuple_type>::parse(std::cin); 
    return 0; 
} 

W drugim kroku możesz zdefiniować szablon funkcji (ponownie), który przekazuje argumenty do właściwej specjalizacji struktury:

template <typename... Args> 
auto parse(const istream &strm) -> decltype(parser<Args...>::parse(strm)) 
{ return parser<Args...>::parse(strm); } 

A teraz można go używać w taki sposób chciał:

int main() 
{ 
    typedef tuple<A,B> tuple_type; 
    auto tup = parse<tuple_type>(std::cin); 
    return 0; 
} 

(i nadal można go używać w starym stylu, zbyt. auto tup = parse<A,B>(std::cin))


uwaga. Jak wspomniano w komentarzu do parser :: parse(), użyłem bezpośredniej konstrukcji krotki zamiast make_tuple, aby uniknąć problemów z kolejnością konstrukcji elementów krotki. Nie jest to bezpośrednio związane z twoim pytaniem, ale dobrą rzeczą do zrobienia. Zobacz how to avoid undefined execution order for the constructors when using std::make_tuple.

+0

"Niestety tego rodzaju specjalizacja nie jest możliwa w szablonach funkcji." nie jest dokładnie prawdą. Cóż, to prawda, ale można uzyskać bardzo podobny efekt z przeciążenia i odliczenia szablonów. Zobacz tutaj: http://liveworkspace.org/code/MjU4Nj$0 – rici

+0

@rici Nie sądzę, że możesz rozwiązać problem opisany w pytaniu używając przeciążenia, ponieważ istniejąca wersja 'parsowania', która musiałaby pozostać nietknięta , jest tak ogólny, że spowodowałby niejasności. Ale mechanizm stosowany w przypadku klas/klas jest mechanizmem częściowej specjalizacji szablonu, który umożliwia ujednoznacznienie, jak opisano powyżej. – jogojapan

+0

TBH, nie mam pojęcia, co chce zrobić. Przeczytałem to pytanie kilka razy i nadal nie rozumiem, jaki interfejs chciałby przedstawić.Z pewnością można jednak zastosować dedukcję, dodając do analizy nieoceniony argument, który czasami jest przydatną techniką. (np. 'auto a = parsowanie (ifs, Into ());', które można dodać bez zmiany istniejącego interfejsu.) (Zgadzam się, że w tym przypadku struktura jest lepsza, dlatego użyłem jej sam :)) – rici

1

Podstawowym podejściem jest stworzenie sekwencji indeksów 0, ..., std::tuple_size<Tuple>::value - 1 jako opakowanie parametru Indices i nazywają swoją funkcję z parse<typename std::tuple_element<Tuple, Indices>::type...>(stream). Prawdopodobnie enkapsulujcie logikę w funkcję parse_tuple<Tuple>(stream) (i jakąś funkcję do której deleguje), która ostatecznie deleguje na parse<...>(stream).

Po pierwsze, tutaj jest szablon klasy i funkcja do tworzenia sekwencji indeksów w oparciu o rozmiar std::tuple. Potrzebne są indeksy aby uzyskać listę typu od std::tuple:

template <int... Indices> struct indices; 
template <> 
struct indices<-1> {    // for an empty std::tuple<> there is no entry 
    typedef indices<> type; 
}; 
template <int... Indices> 
struct indices<0, Indices...> {  // stop the recursion when 0 is reached 
    typedef indices<0, Indices...> type; 
}; 
template <int Index, int... Indices> 
struct indices<Index, Indices...> { // recursively build a sequence of indices 
    typedef typename indices<Index - 1, Index, Indices...>::type type; 
}; 

template <typename T> 
typename indices<std::tuple_size<T>::value - 1>::type const* 
make_indices() { 
    return 0; 
} 

Mając to na miejscu, jest to dość łatwe do wyodrębnienia sekwencję typów z std::tuple<T...>:

template<typename T, int... Indices> 
T parse_tuple(std::istream &stream, indices<Indices...> const*) { 
    return parse<typename std::tuple_element<Indices, T>::type...>(stream); 
} 
template <typename T> 
T parse_tuple(std::istream& stream) { 
    return parse_tuple<T>(stream, make_indices<T>()); 
} 
1

Jest to standard idiom dla tego rodzaju rzeczy. [1]

// Define the "shape" of the template 
template<typename Tuple> struct TupleMap; 
// Specialize it for std::tuple 
template<typename...T> struct TupleMap<std::tuple<T...>> { 
    using type = std::tuple<T...>; // not necessary but saves typing 
    // ... inside here, you have access to the parameter pac 
} 

Oto przykład użycia go, co może lub nie może dopasować swoje oczekiwania (Twój przykład naprawdę nie wskazują planowanej używania, ponieważ brakuje typedef obiecasz w swoim pytaniu): liveworkspace.org .

Po podniesieniu punktu litb możliwe jest wymuszenie skonstruowania komponentów krotki w kolejności od lewej do prawej, ilustrując inny interesujący idiom: dziedziczenie grzebieni. Zobacz lws.

(Od LWS może zniknąć znowu, kto wie, będę wkleić kod tutaj również):

#include <iostream> 
#include <tuple> 
#include <type_traits> 
#include <utility> 

// Define the "shape" of the template 
template<typename Tuple> struct TupleMap; 
// Specialize it for std::tuple 
template<typename...T> struct TupleMap<std::tuple<T...>> { 
    using type = std::tuple<T...>; // not necessary but saves typing 

    type value; 

    template<typename Arg> 
    TupleMap(Arg&& arg) 
     : value(T(std::forward<Arg>(arg))...) { 
    } 

    operator type() { return value; } 
}; 

//Try it out: 
using std::get; // Note 2 
using Numbers = std::tuple<char, double, int>; 

// Note 3 
std::ostream& operator<<(std::ostream& out, const Numbers& n) { 
    return out << get<0>(n) << ' ' << get<1>(n) << ' ' << get<2>(n); 
} 

int main() { 
    std::cout << TupleMap<Numbers>(93.14159); 
    return 0; 
} 

[1] Przynajmniej, myślę, że to norma idiom. Używam go bardzo często i myślę o nim jako o wzorze "otwieracza do puszek".

[2] Jest to konieczne (a przynajmniej jest to mój styl), aby umożliwić korzystanie z get z szablonami podobnymi do krotek zdefiniowanymi poza std. Robiąc to w ten sposób, ADL może znaleźć odpowiednią definicję get, nie zmuszając mnie do dodania specjalizacji do std::get.W ten sposób jest podobny do standardowego idiomu ADL dla begin i end.

[3] Możesz przeszukać SO, aby uzyskać fajny hack, aby specjalizować operator<< dla wszystkich krotek. Jest prostszy, który może być użyty dla konkretnych krotek, ale to wszystko jest poza tematem dla tego pytania, więc po prostu zrobiłem coś łatwego i bez zależności. Zauważ, że to działa, ponieważ operatora konwersji w TupleMap

+0

+1 za korzystanie z struct dla specjalizacji. (Powodem, dla którego napisałem własną odpowiedź jest to, że myślę, że funkcja 'parsuj 'z kodu OP powinna tłumaczyć na statyczną funkcję struktury, a nie wywołanie konstruktora struktury.) – jogojapan

+0

@jogojapan uzasadniona decyzja projektowa. Nie widzę jednak, żeby robiło to wielką różnicę. Ale smaki są różne. Możesz pomyśleć o punkcie litb o kolejności budowy, chyba że już spojrzałeś na moją wersję na LWS. – rici

Powiązane problemy