support - iso c++




Tupla con tag C++ 11 (6)

C ++ non ha un tipo struct che può essere iterabile come una tuple ; è o / o.

Il più vicino che puoi ottenere è tramite l' adattatore struct di Boost.Fusion. Questo ti permette di usare una struct come sequenza Fusion. Naturalmente, questo utilizza anche una serie di macro e richiede di elencare i membri della struct in modo esplicito nell'ordine in cui si desidera eseguirne l'iterazione. Nell'intestazione (supponendo che si desideri eseguire iterazioni sulla struttura in molte unità di traduzione).

In realtà il mio esempio è probabilmente un po 'irrealistico da implementare. Cosa ne pensi di questo?

Potresti implementare qualcosa del genere, ma quegli identificatori devono essere effettivamente tipi o variabili o qualcosa del genere.

Le tuple in C ++ 11 sono belle, ma hanno due svantaggi hude per me, accedere ai membri per indice è

  1. illeggibile
  2. difficoltà a mantenere (se aggiungo un elemento nel mezzo della tupla, sono fregato)

In sostanza quello che voglio ottenere è questo

tagged_tuple <name, std::string, age, int, email, std::string> get_record (); {/*...*/}
// And then soomewhere else

std::cout << "Age: " << get_record().get <age> () << std::endl;

Qualcosa di simile (tipo tagging) è implementato in boost :: property_map, ma non riesco a capire come implementarlo in una tupla con numero arbitrario di elementi

PS Per favore non suggerire di definire un enum con indecie di elementi tuple.

UPD OK, ecco una motivazione. Nei miei progetti ho bisogno di poter definire tante tuple "al volo" e tutte devono avere alcune funzioni e operatori comuni. Questo non è possibile ottenere con le strutture

UPD2 In realtà il mio esempio è probabilmente un po 'irrealistico da implementare. Cosa ne pensi di questo?

tagged_tuple <tag<name, std::string>, tag<age, int>, tag<email, std::string>> get_record (); {/*...*/}
// And then soomewhere else

std::cout << "Age: " << get_record().get <age> () << std::endl;

Ecco un altro modo per farlo, è un po 'più brutto definire i tipi ma aiuta a prevenire gli errori in fase di compilazione perché definisci le coppie con una classe type_pair (molto simile a std::map ). L'aggiunta di un assegno per assicurarsi che le tue chiavi / nome siano uniche al momento della compilazione è il passaggio successivo

Uso:

   using user_t = tagged_tuple<type_pair<struct name, std::string>, type_pair<struct age, int>>;
  // it's initialized the same way as a tuple created with the value types of the type pairs (so tuple<string, int> in this case)
  user_t user  { "chris", 21 };
  std::cout << "Name: " << get<name>(user) << std::endl;
  std::cout << "Age: " << get<age>(user) << std::endl;
 // you can still access properties via numeric indexes as if the class was defined as tuple<string, int>
  std::cout << "user[0] = " << get<0>(user) << std::endl;

Ho optato per essere una funzione membro per mantenerla simile a std :: tuple possibile ma potresti facilmente aggiungerne uno alla classe. Codice sorgente qui


Ho "risolto" un problema simile nel codice di produzione. Per prima cosa, ho una struttura normale (in realtà una classe con varie funzioni membro, ma sono solo i membri dati a cui siamo interessati qui) ...

class Record
{
    std::string name;
    int age;
    std::string email;
    MYLIB_ENABLE_TUPLE(Record) // macro
};

Quindi appena sotto la definizione della struct, ma al di fuori di qualsiasi namespace, ho un'altra macro:

MYLIB_DECLARE_TUPLE(Record, (o.name, o.age, o.email))

Lo svantaggio di questo approccio è che i nomi dei membri devono essere elencati due volte, ma questo è il migliore che sono stato in grado di ottenere pur consentendo la sintassi di accesso ai membri tradizionali all'interno delle funzioni membro della struttura stessa. La macro appare molto vicina alle definizioni degli stessi membri dei dati, quindi non è troppo difficile tenerli sincronizzati l'uno con l'altro.

In un altro file di intestazione ho un modello di classe:

template <class T>
class TupleConverter;

La prima macro è definita in modo tale da dichiarare questo modello come friend della struttura, in modo che possa accedere ai suoi membri di dati privati:

#define MYLIB_ENABLE_TUPLE(TYPE) friend class TupleConverter<TYPE>;

La seconda macro è definita in modo da introdurre una specializzazione del modello:

#define MYLIB_DECLARE_TUPLE(TYPE, MEMBERS) \
    template <>                            \
    class TupleConverter<TYPE>             \
    {                                      \
        friend class TYPE;                 \
        static auto toTuple(TYPE& o)       \
            -> decltype(std::tie MEMBERS)  \
        {                                  \
            return std::tie MEMBERS;       \
        }                                  \
    public:                                \
        static auto toTuple(TYPE const& o) \
            -> decltype(std::tie MEMBERS)  \
        {                                  \
            return std::tie MEMBERS;       \
        }                                  \
    };

Questo crea due overload dello stesso nome di funzione membro, TupleConverter<Record>::toTuple(Record const&) che è pubblico, e TupleConverter<Record>::toTuple(Record&) che è privato e accessibile solo alla Record stessa attraverso l'amicizia. Entrambi restituiscono il loro argomento convertito in una tupla di riferimenti a membri di dati privati ​​tramite std::tie . Il sovraccarico const pubblico restituisce una tupla di riferimenti a const, il sovraccarico privato non const restituisce una tupla di riferimenti a non-const.

Dopo la sostituzione del preprocessore, entrambe le dichiarazioni di friend riferiscono a entità definite nello stesso file di intestazione, quindi non dovrebbe esserci alcuna possibilità che altri codici abusino dell'amicizia per interrompere l'incapsulamento.

toTuple non può essere una funzione membro di Record , poiché il suo tipo di ritorno non può essere dedotto fino a quando non viene completata la definizione di Record .

L'utilizzo tipico è simile a questo:

// lexicographical comparison
bool operator< (Record const& a, Record const& b)
{
    return TupleConverter<Record>::toTuple(a) < TupleConverter<Record>::toTuple(b);
}

// serialization
std::ostream& operator<< (std::ostream& os, Record const& r)
{
    // requires template<class... Ts> ostream& operator<<(ostream&, tuple<Ts...>) defined elsewhere
    return os << TupleConverter<Record>::toTuple(r);
}

Ci sono molti modi in cui questo potrebbe essere esteso, ad esempio aggiungendo un'altra funzione membro in TupleConverter che restituisce un std::vector<std::string> dei nomi dei membri dei dati.

Se mi fosse stato permesso di utilizzare macro variadiche, la soluzione avrebbe potuto essere ancora migliore.


Ho implementato "c ++ named tuple" usando il preprocessore boost. Si prega di vedere l'utilizzo del campione di seguito. Derivando dalla tupla, ottengo confronti, stampa, hash, serializzazione gratis (supponendo che siano definiti per tupla).

#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/comma_if.hpp>


#define CM_NAMED_TUPLE_ELEMS_ITR(r, xxx, index, x ) BOOST_PP_COMMA_IF(index) BOOST_PP_TUPLE_ELEM(2,0,x) 
#define CM_NAMED_TUPLE_ELEMS(seq) BOOST_PP_SEQ_FOR_EACH_I(CM_NAMED_TUPLE_ELEMS_ITR, "dum", seq)
#define CM_NAMED_TUPLE_PROPS_ITR(r, xxx, index, x) \
      BOOST_PP_TUPLE_ELEM(2,0,x) BOOST_PP_CAT(get_, BOOST_PP_TUPLE_ELEM(2,1,x))() const { return get<index>(*this); } \
      void BOOST_PP_CAT(set_, BOOST_PP_TUPLE_ELEM(2,1,x))(const BOOST_PP_TUPLE_ELEM(2,0,x)& oo) { get<index>(*this) = oo; }
#define CM_NAMED_TUPLE_PROPS(seq) BOOST_PP_SEQ_FOR_EACH_I(CM_NAMED_TUPLE_PROPS_ITR, "dum", seq)
#define cm_named_tuple(Cls, seq) struct Cls : tuple< CM_NAMED_TUPLE_ELEMS(seq)> { \
        typedef tuple<CM_NAMED_TUPLE_ELEMS(seq)> Base;                      \
        Cls() {}                                                            \
        template<class...Args> Cls(Args && ... args) : Base(args...) {}     \
        struct hash : std::hash<CM_NAMED_TUPLE_ELEMS(seq)> {};            \
        CM_NAMED_TUPLE_PROPS(seq)                                           \
        template<class Archive> void serialize(Archive & ar, arg const unsigned int version)() {                                                    \
          ar & boost::serialization::base_object<Base>(*this);                              \
        }                                                                   \
      }

//
// Example:
//
// class Sample {
//   public:
//   void do_tata() {
//     for (auto& dd : bar2_) {
//       cout << dd.get_from() << " " << dd.get_to() << dd.get_tata() << "\n";
//       dd.set_tata(dd.get_tata() * 5);
//     }
//     cout << bar1_ << bar2_ << "\n";
//   }
//
//   cm_named_tuple(Foo, ((int, from))((int, to))((double, tata)));  // Foo == tuple<int,int,double> with named get/set functions
//
//   unordered_set<Foo, Foo::hash> bar1_;
//   vector<Foo> bar2_;  
// };

Si noti che l'esempio di codice sopra riportato presuppone che siano state definite funzioni di stampa "generiche" per il vettore / tupla / unordered_set.


I veri problemi che devi risolvere qui sono:

  • I tag sono obbligatori o facoltativi?
  • I tag sono unici? È applicato al momento della compilazione?
  • In quale ambito risiede il tag? Il tuo esempio sembra dichiarare i tag all'interno dello scope dichiarante anziché incapsulato nel tipo, che potrebbe non essere ottimale.

ecatmur proposto una buona soluzione; ma i tag non sono incapsulati e la dichiarazione dei tag è in qualche modo maldestra. C ++ 14 introdurrà l' indirizzamento tuple per tipo , che semplificherà la sua progettazione e garantirà l'unicità dei tag, ma non risolverà il loro scopo.

Boost Fusion Map può anche essere usato per qualcosa di simile, ma ancora una volta, dichiarare che i tag non sono l'ideale.

Esiste una proposta per qualcosa di simile sul forum della proposta standard c ++ , che semplificherebbe la sintassi associando direttamente un nome al parametro del template.

Questo collegamento elenca diversi modi di implementare questo (inclusa la soluzione di ecatmur ) e presenta un caso d'uso diverso per questa sintassi.


Non sono a conoscenza di alcuna classe esistente che lo faccia, ma è abbastanza facile lanciare qualcosa insieme usando una std::tuple e una lista di caratteri di indicizzazione:

#include <tuple>
#include <iostream>

template<typename... Ts> struct typelist {
  template<typename T> using prepend = typelist<T, Ts...>;
};

template<typename T, typename... Ts> struct index;
template<typename T, typename... Ts> struct index<T, T, Ts...>:
  std::integral_constant<int, 0> {};
template<typename T, typename U, typename... Ts> struct index<T, U, Ts...>:
  std::integral_constant<int, index<T, Ts...>::value + 1> {};

template<int n, typename... Ts> struct nth_impl;
template<typename T, typename... Ts> struct nth_impl<0, T, Ts...> {
  using type = T; };
template<int n, typename T, typename... Ts> struct nth_impl<n, T, Ts...> {
  using type = typename nth_impl<n - 1, Ts...>::type; };
template<int n, typename... Ts> using nth = typename nth_impl<n, Ts...>::type;

template<int n, int m, typename... Ts> struct extract_impl;
template<int n, int m, typename T, typename... Ts>
struct extract_impl<n, m, T, Ts...>: extract_impl<n, m - 1, Ts...> {};
template<int n, typename T, typename... Ts>
struct extract_impl<n, 0, T, Ts...> { using types = typename
  extract_impl<n, n - 1, Ts...>::types::template prepend<T>; };
template<int n, int m> struct extract_impl<n, m> {
  using types = typelist<>; };
template<int n, int m, typename... Ts> using extract = typename
  extract_impl<n, m, Ts...>::types;

template<typename S, typename T> struct tt_impl;
template<typename... Ss, typename... Ts>
struct tt_impl<typelist<Ss...>, typelist<Ts...>>:
  public std::tuple<Ts...> {
  template<typename... Args> tt_impl(Args &&...args):
    std::tuple<Ts...>(std::forward<Args>(args)...) {}
  template<typename S> nth<index<S, Ss...>::value, Ts...> get() {
    return std::get<index<S, Ss...>::value>(*this); }
};
template<typename... Ts> struct tagged_tuple:
  tt_impl<extract<2, 0, Ts...>, extract<2, 1, Ts...>> {
  template<typename... Args> tagged_tuple(Args &&...args):
    tt_impl<extract<2, 0, Ts...>, extract<2, 1, Ts...>>(
      std::forward<Args>(args)...) {}
};

struct name {};
struct age {};
struct email {};

tagged_tuple<name, std::string, age, int, email, std::string> get_record() {
  return { "Bob", 32, "[email protected]"};
}

int main() {
  std::cout << "Age: " << get_record().get<age>() << std::endl;
}

Probabilmente vorrai scrivere const e rvalue get accessor su quello esistente.





tuples