2017-12-20 6 views
5

Der folgende Code:Ist das Verhalten von std :: get on rvalue-Referenztupeln gefährlich?

#include <tuple> 

int main() 
{ 
    auto f = []() -> decltype (auto) 
    { 
    return std::get<0> (std::make_tuple (0)); 
    }; 

    return f(); 
} 

(Lautlos) generiert Code mit undefiniertem Verhalten - die temporäre rvalue von make_tuple zurückgegeben wird durch die std :: propagiert < bekommen> und durch die decltype (Auto) auf den Rückgabetyp. Es endet damit, dass ein Verweis auf ein temporäres Objekt zurückgegeben wird, das nicht mehr gültig ist. Sehen Sie es hier https://godbolt.org/g/X1UhSw.

Nun könnte man argumentieren, dass meine Verwendung von decltype(auto) ist schuld. Aber in meinem generischen Code (wo der Typ des Tupels std::tuple<Foo &> sein könnte) möchte ich nicht immer eine Kopie machen. Ich möchte wirklich den genauen Wert oder die Referenz aus dem Tupel extrahieren.

Mein Gefühl ist, dass diese Überlastung der std::get gefährlich ist:

template< std::size_t I, class... Types > 
constexpr std::tuple_element_t<I, tuple<Types...> >&& 
get(tuple<Types...>&& t) noexcept; 

Während Verweise auf Tupelelemente lvalue ausbreitende wahrscheinlich sinnvoll ist, ich glaube nicht, dass für rvalue Referenzen hält.

Ich bin mir sicher, dass das Standardkomitee dies sehr sorgfältig durchdacht hat, aber kann mir jemand erklären, warum dies als die beste Option angesehen wurde?

+0

* "Ich denke nicht, dass das für Rvalue Referenzen gilt" * - Es tut, wenn Sie 'forward_as_tuple'. Sie möchten, dass die Wertkategorie beibehalten wird. Oder wenn Sie einen Wert aus einem Rückgabewert einer Funktion extrahieren möchten. – StoryTeller

+0

Danke, aber ich bin nicht wirklich sicher, dass es die Wertkategorie bewahrt. Zum Beispiel, wenn ich eine Funktion habe, die ein 'std :: tuple ' zurückgibt und ich 'std :: get <0>' darauf nenne, würde ich erwarten, ein einfaches 'int' zu erhalten, kein' int && '. Können Sie mir ein konkretes Beispiel geben, wo Sie etwas anderes wollen? –

+1

Wird diese Überladung von 'get' nicht für effektive strukturierte Bindungen benötigt? –

Antwort

2

Betrachten Sie das folgende Beispiel:

void consume(foo&&); 

template <typename Tuple> 
void consume_tuple_first(Tuple&& t) 
{ 
    consume(std::get<0>(std::forward<Tuple>(t))); 
} 

int main() 
{ 
    consume_tuple_first(std::tuple{foo{}}); 
} 

In diesem Fall wissen wir, dass std::tuple{foo{}} vorübergehend ist und dass es für die gesamte Dauer des consume_tuple_first(std::tuple{foo{}}) Ausdruck leben.

Wir wollen jede unnötige Kopie und Bewegung vermeiden, aber die Temporalität von foo{} weiter auf consume übertragen.

Der einzige Weg, das zu tun, ist durch eine Rückkehr std::getrvalue Referenz, wenn sie mit einer temporären std::tuple Instanz aufgerufen.

live example on wandbox


std::get<0>(std::forward<Tuple>(t)) zu std::get<0>(t) Ändern erzeugt einen Übersetzungsfehler (wie erwartet) (on wandbox).


Mit einer get Alternative, die von Wert führt zu einer zusätzlichen unnötigen Bewegung zurück:

template <typename Tuple> 
auto myget(Tuple&& t) 
{ 
    return std::get<0>(std::forward<Tuple>(t)); 
} 

template <typename Tuple> 
void consume_tuple_first(Tuple&& t) 
{ 
    consume(myget(std::forward<Tuple>(t))); 
} 

live example on wandbox


aber kann mir jemand erklären, warum dies in Betracht gezogen wurde die Beste Option?

Weil ermöglicht es optional generischen Code, die sie nahtlos Provisorien ausbreitet rvalue Referenzen wenn Tupel zugreifen. Die Alternative der Rückgabe nach Wert kann zu unnötigen Verschiebeoperationen führen.

+0

Ah ich sehe. Also mein "safe_get", das den schmucklosen Typ zurückgibt, verursacht einen zusätzlichen Zug. IMHO, das wäre ein Preis, der es wert ist, für die Sicherheit zu zahlen, aber ich kann zumindest sehen, dass es umstritten ist. Vielen Dank für deine Antwort! –

Verwandte Themen