2016-05-13 16 views
0

Ich habe die folgende (relativ) einfache Implementierung einer std::tuple zip-Funktion (analog Python zip) mit perfekter Weiterleitung:Clang Fehler "versucht, ein Referenzelement in einem Tupel mit einem rvalue" nicht durch gcc

template <size_t I, size_t N> 
struct tuple_zip_helper { 
    template <typename... Tuples> 
    constexpr auto operator()(Tuples&&... tuples) const { 
    return tuple_cat(
     make_tuple(forward_as_tuple(get<I>(forward<Tuples>(tuples))...)), 
     tuple_zip_helper<I+1, N>()(forward<Tuples>(tuples)...) 
    ); 
    } 
}; 

template <size_t N> 
struct tuple_zip_helper<N, N> { 
    template <typename... Tuples> 
    constexpr auto operator()(Tuples&&...) const { 
    return forward_as_tuple(); 
    } 
}; 

namespace std { 
    // Extend min to handle single argument case, for generality 
    template <typename T> 
    constexpr decltype(auto) min(T&& val) { return forward<T>(val); } 
} 

template <typename... Tuples> 
auto tuple_zip(Tuples&&... tuples) { 
    static constexpr size_t min_size = min(tuple_size<decay_t<Tuples>>::value...); 
    return tuple_zip_helper<0, min_size>()(forward<Tuples>(tuples)...); 
} 

Dies scheint für zwei oder mehr Tupeln zu funktionieren, auch wenn lvalues ​​und rvalues ​​Mischen und selbst wenn ich eine BlabberMouth Klasse verwenden für falsche Kopien und bewegt sich zu überprüfen:

template <typename Tuple> 
void f(Tuple&& tup) { 
    cout << get<0>(get<0>(tup)).data << endl; 
} 

struct Blabbermouth { 
    Blabbermouth(string const& str) : data(str) { } 
    Blabbermouth(Blabbermouth const& other) : data(other.data) { cout << data << " copied" << endl; } 
    Blabbermouth(Blabbermouth&& other) : data(move(other.data)) { cout << data << " moved" << endl; } 
    string data; 
}; 

int main(int argc, char** argv) { 
    Blabbermouth x("hello "); 
    // prints "hello" 
    f(tuple_zip(
     forward_as_tuple(x, 2), 
     forward_as_tuple(Blabbermouth("world"), 3) 
)); 
} 

Es funktioniert auch gut wann Ich gebe es nur eine tuple ohne Mischen lvalues ​​und rvalues ​​(Klirren-3.9, frühere Versionen von Klirren Drossel auf diese auch):

f(tuple_zip(forward_as_tuple(Blabbermouth("world"), 3))); // prints "world" 

Allerdings, wenn ich mischen lvalues ​​und rvalues ​​und nur ein Tupel geben, clang flippt aus über etwas in einer noexecpt Spezifikation (aber gcc ist in Ordnung, und sogar richtig läuft):

auto x = BlabberMouth("hello"); 
f(tuple_zip(forward_as_tuple(x, 3))); // clang freaks out, gcc okay 

Live Demo

Was (wenn überhaupt) mache ich falsch? Sollte gcc sich beschweren, oder sollte sich klingeln nicht beschweren? Hat mein Code irgendwelche falschen Referenzen, mit denen ich gerade "Glück habe", und deshalb protestiert clang? Hätte ich das anders machen sollen? Wenn der Klang hier falsch ist, kann jemand einen Workaround vorschlagen? (Und/oder verknüpfen Sie mich an einen Fehlerbericht?)


aktualisieren

@Oktalist eine viel minimales Beispiel dazu beigetragen, dass das gleiche Problem zeigt:

struct foo {}; 

int main(int argc, char** argv) 
{ 
    foo f; 
    std::tuple<foo&> t(f); 
    std::tuple_cat(std::make_tuple(t), std::make_tuple()); 
} 

(ich gedacht hatte machen mein Beispiel auch minimal, aber ich war mir nicht sicher, ob das, was ich tat, genau analog war, hauptsächlich weil ich nicht ganz verstehe, wie perfektes Forwarding mit auto/decltype(auto) Rückgabewerten, Rückgabewertoptimierung (RVO) interagiert. std::get und std::make_tuple, so wollte ich sicher sein, ich nicht sonst dumm, etwas zu tun war)

+3

[Minimal Beispiel mit dem gleichen Problem] (https://godbolt.org/g/3ZzuJ6) – Oktalist

+3

Klingt wie https://llvm.org/bugs/show_bug.cgi?id=22806, im letzten Monat behoben. @Oktalists minimiertes Beispiel funktioniert bei trunk. –

+0

@ T.C.Das ist großartig, aber es ist für uns im Grunde unmöglich, von unseren Benutzern zu verlangen, dass sie den Klangstamm benutzen. Können Sie eine Problemumgehung vorschlagen, die keine falschen Kopien generiert? –

Antwort

1

Der Fehler durch die Anrufe tuple_cat (wenn auch nicht genau darin verursacht wird. Es scheint, dass einige Unordnung in libC++ tuple byzantinischen Labyrinth von Konstruktoren und SFINAE Bedingungen), so die Problemumgehung ist es zu vermeiden, es zu verwenden.

Es gibt keinen Grund rekursiv zu machen tuple_cat s sowieso; Eine Erweiterung (oder zwei) reicht aus.

template<size_t I, typename... Tuples> 
constexpr auto tuple_zip_one(Tuples&&... tuples) { 
    return forward_as_tuple(get<I>(forward<Tuples>(tuples))...); 
} 

template<size_t...Is, typename... Tuples> 
constexpr auto tuple_zip_helper(index_sequence<Is...>, Tuples&&... tuples) { 
    return make_tuple(tuple_zip_one<Is>(forward<Tuples>(tuples)...)...); 
} 

template <typename... Tuples> 
auto tuple_zip(Tuples&&... tuples) { 
    static constexpr size_t min_size = min({tuple_size<decay_t<Tuples>>::value...}); 
    return tuple_zip_helper(make_index_sequence<min_size>(), forward<Tuples>(tuples)...); 
} 

nahm ich mir die Freiheit Ihre UB-Induktion min Überlastung zu entfernen und einfach die Standard initializer_list Version stattdessen verwenden.

Demo.