make_tuple - tuple c++ example




Rendi personalizzato il tipo "tie-able"(compatibile con std:: tie) (3)

Perché i tentativi attuali falliscono

std::tie(a, b) produce una std::tuple<int&, string&> . Questo tipo non è correlato a std::tuple<int, string> ecc.

std::tuple<T...> s hanno diversi assegnatari-operatori:

  • Un operatore di assegnazione predefinito, che accetta una std::tuple<T...>
  • Un modello di operatore di assegnazione di tuple-converting con un pacchetto di parametri di tipo U... , che accetta una std::tuple<U...>
  • Un modello di operatore di assegnazione della conversione della coppia con due parametri di tipo U1, U2 , che accetta una std::pair<U1, U2>

Per quelle tre versioni esistono varianti di copia e spostamento; aggiungi un const& o o un && ai tipi che prendono.

I modelli di operatore di assegnazione devono dedurre i loro argomenti del modello dal tipo di argomento della funzione (cioè del tipo di RHS dell'espressione di assegnazione).

Senza un operatore di conversione in Foo , nessuno di questi operatori di assegnazione è utilizzabile per std::tie(a,b) = foo . Se aggiungi un operatore di conversione a Foo , solo l'operatore di assegnazione predefinito diventa praticabile: la detrazione del tipo di modello non prende in considerazione le conversioni definite dall'utente. Cioè, non è possibile dedurre argomenti modello per i modelli di operatore di assegnazione dal tipo Foo .

Poiché solo una conversione definita dall'utente è consentita in una sequenza di conversione implicita, il tipo in cui l'operatore di conversione viene convertito deve corrispondere esattamente al tipo di operatore di assegnazione predefinito. Cioè, deve usare esattamente gli stessi tipi di elementi tuple come risultato di std::tie .

Per supportare le conversioni dei tipi di elementi (ad es. Assegnazione di Foo::a a a long ), l'operatore di conversione di Foo deve essere un modello:

struct Foo {
    int a;
    string b;
    template<typename T, typename U>
    operator std::tuple<T, U>();
};

Tuttavia, i tipi di elementi di std::tie sono riferimenti. Dal momento che non si dovrebbe restituire un riferimento a un temporaneo, le opzioni per le conversioni all'interno del modello di operatore sono piuttosto limitate (heap, tipo punning, static, thread locale, ecc.).

Considera che ho un tipo personalizzato (che posso estendere):

struct Foo {
    int a;
    string b;
};

Come posso rendere un'istanza di questo oggetto assegnabile a uno std::tie , ovvero std::tuple di riferimenti?

Foo foo = ...;

int a;
string b;

std::tie(a, b) = foo;

Tentativi falliti:

Sovraccaricare l'operatore di assegnazione per tuple<int&,string&> = Foo non è possibile, poiché l'operatore di assegnazione è uno degli operatori binari che devono essere membri dell'oggetto lato sinistro.

Così ho cercato di risolvere questo problema implementando un operatore di conversione tupla adatto. Le seguenti versioni falliscono:

  • operator tuple<int,string>() const
  • operator tuple<const int&,const string&>() const

Risultano in errore nel compito, dicendo che " operator = non è sovraccarico per tuple<int&,string&> = Foo ". Immagino che questo sia dovuto al fatto che "la conversione a qualsiasi tipo X + deducendo il parametro del modello X per operator =" non funziona insieme, solo uno di essi contemporaneamente.

Tentativo imperfetto:

Quindi ho cercato di implementare un operatore di conversione per il tipo esatto del legame :

  • operator tuple<int&,string&>() const Demo
  • operator tuple<int&,string&>() Demo

Il compito ora funziona poiché i tipi sono ora (dopo la conversione) esattamente uguali, ma questo non funzionerà per tre scenari che vorrei supportare:

  1. Se il legame ha variabili di tipi diversi ma convertibili vincolati (cioè cambia int a; long long a; sul lato client), fallisce poiché i tipi devono corrispondere completamente. Ciò contraddice l'uso usuale dell'assegnazione di una tupla a una tupla di riferimenti che consente tipi convertibili. (1)
  2. L'operatore di conversione deve restituire un legame a cui devono essere dati i riferimenti di valore. Questo non funzionerà con valori temporanei o membri const. (2)
  3. Se l'operatore di conversione non è const, l'assegnazione fallisce anche per un const Foo sul lato destro. Per implementare una versione const della conversione, è necessario modificare la costanza dei membri del soggetto const. Questo è brutto e potrebbe essere abusato, con conseguente comportamento indefinito.

Vedo solo un'alternativa nel fornire la mia funzione di tie + classe insieme ai miei oggetti "legabili", il che mi costringe a duplicare la funzionalità di std::tie che non mi piace (non che trovo difficile fallo, ma è sbagliato doverlo fare).

Penso che alla fine della giornata, la conclusione è che questo è uno svantaggio di un'implementazione di tuple solo in libreria. Non sono così magici come vorremmo che fossero.

MODIFICARE:

A quanto pare, non sembra esserci una soluzione reale che risolva tutti i problemi di cui sopra. Una buona risposta spiegherebbe perché questo non è risolvibile. In particolare, vorrei che qualcuno facesse luce sul motivo per cui i "tentativi falliti" non possono funzionare.

(1): Un trucco orribile è scrivere la conversione come modello e convertire i tipi di membri richiesti nell'operatore di conversione. È un trucco orribile perché non so dove conservare questi membri convertiti. In questa demo uso variabili statiche, ma questo non è rientrante nel thread.

(2): può essere applicato lo stesso hack di cui al punto (1).


Ci sono solo due modi in cui puoi provare ad andare:

  1. Utilizza gli operatori di assegnazione dei modelli:
    È necessario derivare pubblicamente da un tipo che l'operatore di assegnazione di modelli corrisponde esattamente.
  2. Utilizza gli operatori di assegnazione non basati su modelli:
    Offri una conversione non explicit al tipo previsto dal gestore di copia non basato su modello, quindi verrà utilizzato.
  3. Non c'è una terza opzione.

In entrambi i casi, il tuo tipo deve contenere gli elementi che vuoi assegnare, in nessun modo intorno ad esso.

#include <iostream>
#include <tuple>
using namespace std;

struct X : tuple<int,int> {
};

struct Y {
    int i;
    operator tuple<int&,int&>() {return tuple<int&,int&>{i,i};}
};

int main()
{
    int a, b;
    tie(a, b) = make_tuple(9,9);
    tie(a, b) = X{};
    tie(a, b) = Y{};
    cout << a << ' ' << b << '\n';
}

Su coliru: http://coliru.stacked-crooked.com/a/315d4a43c62eec8d


Questo tipo di fa quello che vuoi, giusto? (si presume che i tuoi valori possano essere collegati ai tipi di corso ...)

#include <tuple>
#include <string>
#include <iostream>
#include <functional>
using namespace std;


struct Foo {
    int a;
    string b;

    template <template<typename ...Args> class tuple, typename ...Args>
    operator tuple<Args...>() const {
        return forward_as_tuple(get<Args>()...);
    }

    template <template<typename ...Args> class tuple, typename ...Args>
    operator tuple<Args...>() {
        return forward_as_tuple(get<Args>()...);
    }

    private:
    // This is hacky, may be there is a way to avoid it...
    template <typename T>
    T get()
    { static typename remove_reference<T>::type i; return i; }

    template <typename T>
    T get() const
    { static typename remove_reference<T>::type i; return i; }

};

template <>
int&
Foo::get()
{ return a; }

template <>
string&
Foo::get()
{ return b; }

template <>
int&
Foo::get() const
{ return *const_cast<int*>(&a); }

template <>
string&
Foo::get() const
{ return *const_cast<string*>(&b); }

int main() {
    Foo foo { 42, "bar" };
    const Foo foo2 { 43, "gah" };

    int a;
    string b;

    tie(a, b) = foo;
    cout << a << ", " << b << endl;

    tie(a, b) = foo2;
    cout << a << ", " << b << endl;

}

Lo svantaggio principale è che a ciascun membro è possibile accedere solo dai tipi, ora è possibile aggirare questo con qualche altro meccanismo (ad esempio, definire un tipo per membro e avvolgere il riferimento al tipo in base al tipo di membro che si desidera accesso..)

In secondo luogo l'operatore di conversione non è esplicito, si convertirà in qualsiasi tipo di tupla richiesta (potrebbe essere che tu non vuoi che ..)

Il vantaggio principale è che non devi specificare esplicitamente il tipo di conversione, è tutto dedotto ...





std