2015-11-30 12 views
7

in C++ 11 Ich habe sehr ordentlich und Arbeits Code std::tuple Artikel nach Typ zum Extrahieren (Als ich diese Funktion auch placed kennen zu C++ 14 stl)std :: tuple, erhalten Artikel von ererbten Typ

jetzt bin ich mit der Aufgabe, vor dem Code auszuwählen Element durch die geerbte Klasse Spezifikation

struct A 
{ 
    int a; 
}; 
struct B : public A 
{ 
    int b; 
}; 

... 
auto tval = std::make_tuple(1, B()); 
//now I would like to reference items as following: 
tuple_ref_by_inheritance<A>(tval).a = 5; //Access to B instance by parent A 

Es folgt mein erfolglos Versuch:

template< class T, class Tuple > 
    struct tuple_ref_index; 

    // recursive case 
    template<class T, class Head, class... Tail > 
    struct tuple_ref_index<T, std::tuple<Head, Tail...> > 
    { 
     enum { value = tuple_ref_index<T, std::tuple<Tail...>>::value + 1 }; 
    }; 

    template<class T, class Head, class... Tail > 
    struct tuple_ref_index<T, std::tuple<Head, Tail...> > 
    { 
     const static typename std::enable_if< 
       std::is_same<T, Head>::value>::type* _= nullptr; 
     enum { value = 0 }; 
    }; 

    template <class T, class Tuple> 
    inline T& tuple_ref_by_inheritance(Tuple& tuple) 
    { 
     return std::get< tuple_ref_index<T, Tuple>::value >(tuple); 
    } 
+0

Was ist der Anwendungsfall die '1' für mit? Erwarten Sie mehr als eine abgeleitete Klasse in dem Tupel, aus dem Sie auswählen möchten? – AndyG

+0

@AndyG nur ein Beispiel für Tupel, die neben A/B einen anderen Wert haben. (2) Ich verspreche, verwende nicht 2 absteigend von einer Schnittstelle – Dewfy

Antwort

5
#include <type_traits> 
#include <utility> 
#include <cstddef> 
#include <tuple> 

template <typename Base, typename Tuple, std::size_t I = 0> 
struct tuple_ref_index; 

template <typename Base, typename Head, typename... Tail, std::size_t I> 
struct tuple_ref_index<Base, std::tuple<Head, Tail...>, I> 
    : std::conditional<std::is_base_of<Base, Head>::value 
        , std::integral_constant<std::size_t, I> 
        , tuple_ref_index<Base, std::tuple<Tail...>, I+1> 
        >::type 
{ 
}; 

template <typename Base, typename Tuple> 
auto tuple_ref_by_inheritance(Tuple&& tuple) 
    -> decltype(std::get<tuple_ref_index<Base, typename std::decay<Tuple>::type>::value>(std::forward<Tuple>(tuple))) 
{ 
    return std::get<tuple_ref_index<Base, typename std::decay<Tuple>::type>::value>(std::forward<Tuple>(tuple)); 
} 

DEMO

+0

Warum eine manuelle Überlast hinzufügen? Ist es nicht möglich, '&&' und 'std :: forward' zu verwenden? – SirGuy

+0

@GuyGreer Ich wollte es sauber zu halten, aber gut, ich habe es mit einer Weiterleitung-Referenz-Version ersetzt –

+0

Sorry, ich wollte nicht Ihre Hand zwingen, und es sauber zu halten scheint ein guter Grund für mich. – SirGuy

3

Zuerst einige Metaprogrammierungstafel.

void_t ist C++ 14:

namespace details { 
    template<class...>struct voider{using type=void;}; 
} 

template<class...Ts> 
using void_t=typename details::voider<Ts...>::type; 

dieses auf jedes Element einer Liste, um eine Testläufe, und gibt den ersten Test, der vergeht:

template<template<class...>class Test, class List> 
struct get_first_that_passes; 

template<template<class...>class Test, class List> 
using get_first_that_passes_t= 
    typename get_first_that_passes<Test,List>::type; 

namespace details { 
    template<template<class...>class, class, class...> 
    struct get_first_pass {}; 

    template<template<class...>class Test, class T0, class...Ts> 
    struct get_first_pass<Test, std::enable_if_t< !Test<T0>::value >, T0, Ts...> : 
    get_first_pass<Test, void, Ts...> 
    {}; 

    template<template<class...>class Test, class T0, class...Ts> 
    struct get_first_pass<Test, std::enable_if_t< Test<T0>::value >, T0, Ts...> { 
    using type=T0; 
    }; 
} 

template<template<class...>class Test, template<class...>class List, class...Ts> 
struct get_first_that_passes<Test, List<Ts...>>: 
    details::get_first_pass<Test, void, Ts...> 
{}; 

Jetzt schreiben wir is_derived_from, die wenn erzeugt einen Test etwas von einer Basis abgeleitet:

template<class Base> 
struct is_derived_from { 
    template<class Derived> 
    using test = std::is_base_of<Base,Derived>; 
}; 

Composing die ab ove zwei, erhalten wir die erste Art in einer Liste, die von einer Basis abgeleitet:

template<class Base, class List> 
using get_first_derived = 
    get_first_that_passes_t< 
    is_derived_from<Base>::template test, 
    List 
    >; 

, die lässt uns ein einfaches get_from_base<T>(tuple) schreiben, die die erste Art in einem tuple bekommt, die von T leitet, dann ruft std::get<T> auf es:

template<class Base, class Tuple> 
auto get_from_base(Tuple&& tuple) 
->decltype(std::get< get_first_derived<Base, std::decay_t<Tuple>> >(std::forward<Tuple>(tuple))) 
    { return std::get< get_first_derived<Base, std::decay_t<Tuple>> >(std::forward<Tuple>(tuple)); } 

übersetzt dies zu C++ 11 ist als Übung verlassen. (Entfernen Sie die _t s ist wahrscheinlich genug).

Live example.

Beachten Sie, dass, wie geschrieben, wird der erste Typ von Base abgeleitet. Dann wird ein Verweis auf dieses Element zurückgegeben, aber nur, wenn keine weiteren Instanzen von in der Liste sind.

get<Type> Passend, dann würden Sie den Schwanz hat keine anderen Typen von Base abgeleitet bestätigen möchten. Wenn Sie get_first_that_derives_from möchten, müssen Sie den Index anstelle des Typs abrufen.

+0

großartig! Danke +1! – Dewfy