Was ist std :: zerfall und wann sollte es verwendet werden?

184

Was sind die Gründe für die Existenz von std::decay? In welchen Situationen ist das std::decaysinnvoll?

Eric Javier Hernandez Saura
quelle
3
Es wird in der Standardbibliothek verwendet, z. B. wenn Argumente an einen Thread übergeben werden. Diese müssen nach Wert gespeichert werden, damit Sie zB Arrays nicht speichern können. Stattdessen wird ein Zeiger gespeichert und so weiter. Es ist auch eine Metafunktion, die die Anpassungen der Funktionsparametertypen nachahmt.
Dyp
3
decay_t<decltype(...)>ist eine schöne Kombination, um zu sehen, was autosich daraus ergeben würde.
Marc Glisse
58
Radioaktive Variablen? :)
saiarcot895
7
std :: zerfall () kann drei Dinge tun. 1 Es kann ein Array von T in T * konvertieren. 2. Es kann Lebenslaufqualifizierer und Referenz entfernen; 3. Es wandelt die Funktion T in T * um. zB Zerfall (void (char)) -> void (*) (char). Anscheinend hat niemand die dritte Verwendung in den Antworten erwähnt.
12.
1
Gott sei Dank haben wir noch keine Quarks in C ++
Wormer

Antworten:

190

<joke> Es wird offensichtlich verwendet, um radioaktive std::atomicTypen in nicht radioaktive zu zerlegen. </ witz>

N2609 ist das vorgeschlagene Papier std::decay. Das Papier erklärt:

Einfach ausgedrückt decay<T>::typeist dies die Transformation des Identitätstyps, es sei denn, T ist ein Array-Typ oder eine Referenz auf einen Funktionstyp. In diesen Fällen decay<T>::typeergibt das einen Zeiger bzw. einen Zeiger auf eine Funktion.

Das motivierende Beispiel ist C ++ 03 std::make_pair:

template <class T1, class T2> 
inline pair<T1,T2> make_pair(T1 x, T2 y)
{ 
    return pair<T1,T2>(x, y); 
}

die ihre Parameter nach Wert akzeptierte, damit String-Literale funktionieren:

std::pair<std::string, int> p = make_pair("foo", 0);

Wenn es seine Parameter als Referenz akzeptiert hat, T1wird es als Array-Typ abgeleitet und anschließend a erstelltpair<T1, T2> schlecht geformt.

Dies führt jedoch offensichtlich zu erheblichen Ineffizienzen. Daher ist es erforderlich decay, den Satz von Transformationen anzuwenden, der beim Übergeben von Werten auftritt, damit Sie die Effizienz der Referenzübernahme der Parameter erhalten und dennoch die Typtransformationen erhalten, die für die Arbeit Ihres Codes mit Zeichenfolgenliteralen erforderlich sind. Array-Typen, Funktionstypen und dergleichen:

template <class T1, class T2> 
inline pair< typename decay<T1>::type, typename decay<T2>::type > 
make_pair(T1&& x, T2&& y)
{ 
    return pair< typename decay<T1>::type, 
                 typename decay<T2>::type >(std::forward<T1>(x), 
                                            std::forward<T2>(y)); 
}

Hinweis: Dies ist nicht die eigentliche C ++ 11- make_pairImplementierung. C ++ 11 make_pairentpackt auch std::reference_wrappers.

TC
quelle
"T1 wird als Array-Typ abgeleitet, und dann wird die Konstruktion eines Paares <T1, T2> schlecht geformt." Was ist das Problem hier?
Camino
6
Ich verstehe, auf diese Weise erhalten wir das Paar <char [4], int>, das nur Zeichenfolgen mit 4 Zeichen akzeptieren kann
camino
@camino Ich verstehe nicht, sagen Sie, dass der erste Teil des Paares ohne std :: zerfall 4 Bytes für vier Zeichen anstelle eines Zeigers auf char belegen würde? Ist es das, was der std :: forward macht? Verhindert, dass es von einem Array zu einem Zeiger verfällt?
Zebrafisch
3
@Zebrafish Es ist Array-Zerfall. Zum Beispiel: template <Typname T> void f (T &); f ("abc"); T ist char (&) [4], aber die Vorlage <Typname T> void f (T); f ("abc"); T ist char *; Eine Erklärung finden Sie auch hier: stackoverflow.com/questions/7797839/…
camino
67

Wenn Sie mit Vorlagenfunktionen arbeiten, die Parameter eines Vorlagentyps verwenden, haben Sie häufig universelle Parameter. Universelle Parameter sind fast immer Referenzen der einen oder anderen Art. Sie sind auch konstant flüchtig qualifiziert. Daher funktionieren die meisten Typmerkmale nicht wie erwartet:

template<class T>
void func(T&& param) {
    if (std::is_same<T,int>::value) 
        std::cout << "param is an int\n";
    else 
        std::cout << "param is not an int\n";
}

int main() {
    int three = 3;
    func(three);  //prints "param is not an int"!!!!
}

http://coliru.stacked-crooked.com/a/24476e60bd906bed

Die Lösung hier ist zu verwenden std::decay:

template<class T>
void func(T&& param) {
    if (std::is_same<typename std::decay<T>::type,int>::value) 
        std::cout << "param is an int\n";
    else 
        std::cout << "param is not an int\n";
}

http://coliru.stacked-crooked.com/a/8cbd0119a28a18bd

Mooing Duck
quelle
14
Ich bin damit nicht zufrieden. decayist sehr aggressiv, z. B. wenn es auf eine Referenz auf ein Array angewendet wird, ergibt es einen Zeiger. Es ist normalerweise zu aggressiv für diese Art der Metaprogrammierung IMHO.
Dyp
@dyp, was ist dann weniger "aggressiv"? Was sind Alternativen?
Serge Rogatch
5
@SergeRogatch Im Fall von "universellen Parametern" / universellen Referenzen / Weiterleitungsreferenzen würde ich nur remove_const_t< remove_reference_t<T> >möglicherweise eine benutzerdefinierte Metafunktion einschließen.
Dyp
1
Wo wird param überhaupt verwendet? Es ist ein Argument von func, aber ich sehe nicht, dass es irgendwo verwendet wird
Savram
2
@savram: In diesen Codeteilen: ist es nicht. Wir überprüfen nur den Typ, nicht den Wert. Alles sollte gut funktionieren, wenn nicht besser, wenn wir den Namen des Parameters entfernen.
Mooing Duck