Wie implementiere ich die verallgemeinerte Form von std :: same_as (dh für mehr als zwei Typparameter), die unabhängig von der Parameterreihenfolge ist?

8

Hintergrund

Wir wissen, dass das Konzept std::same_asunabhängig von der Ordnung ist (mit anderen Worten, symmetrisch): std::same_as<T, U>äquivalent zu std::same_as<U, T>( verwandte Frage ). In dieser Frage möchte ich etwas Allgemeineres implementieren: template <typename ... Types> concept same_are = ...Das prüft, ob die Typen im Paket Typesgleich sind.

Mein Versuch

#include <type_traits>
#include <iostream>
#include <concepts>

template <typename T, typename... Others>
concept same_with_others = (... && std::same_as<T, Others>);

template <typename... Types>
concept are_same = (... && same_with_others<Types, Types...>);

template< class T, class U> requires are_same<T, U>
void foo(T a, U b) {
    std::cout << "Not integral" << std::endl;
}

// Note the order <U, T> is intentional
template< class T, class U> requires (are_same<U, T> && std::integral<T>)
void foo(T a, U b) {
    std::cout << "Integral" << std::endl;
}

int main() {
    foo(1, 2);
    return 0;
}

(Meine Absicht hier ist es, über jedes mögliche geordnete Paar von Typen in der Packung aufzuzählen)

Leider würde dieser Code nicht kompiliert , und der Compiler beschwert sich, dass der Aufruf von nicht foo(int, int)eindeutig ist. Ich glaube, dass es betrachtet are_same<U, T>und are_same<T, U>als nicht gleichwertig. Ich möchte wissen, warum der Code fehlschlägt, wie ich ihn beheben kann (damit der Compiler sie als gleichwertig behandelt).

Rin Kaenbyou
quelle
Mein Bauch sagt mir, dass es einen Helfer braucht, der same_with_othersjede mögliche Permutation der Typen ausführt .
Geschichtenerzähler - Unslander Monica
Ich bin mir nicht ganz sicher, ob ich dich richtig verstehe. Sie möchten überprüfen, ob alle Arten von gleich ... Typessind? Vielleicht kann Ihnen std :: connection helfen. Am Ende der Seite befindet sich ein Beispiel, das Ihrem Ansatz ähnelt.
Churill
@ StoryTeller-UnslanderMonica Aber ich habe bereits alle möglichen bestellten Typenpaare im Paket aufgelistet. Ist das nicht genug Oder können Compiler ohne konkrete Typen die Äquivalenz von Falten nicht bestimmen?
Rin Kaenbyou
@churill Ich möchte dies in Konzepten implementieren, und die Reihenfolge der Parameter erfordert besondere Sorgfalt in Konzepten.
Rin Kaenbyou
Ich bin mir nicht sicher, daher ist es nur ein Bauchgefühl. Könnte sein, dass die GCC-Entwickler auch noch nicht sicher sind. Könnte auch sein, dass sie es noch nicht vollständig implementiert haben.
Geschichtenerzähler - Unslander Monica

Antworten:

5

Das Problem ist bei diesem Konzept:

template <typename T, typename... Others>
concept are_same = (... && std::same_as<T, Others>);

Ist das die normalisierte Form dieses Konzepts ... genau das. Wir können dies nicht "entfalten" (es gibt nichts zu tun), und die aktuellen Regeln normalisieren sich nicht durch "Teile" eines Konzepts.

Mit anderen Worten, damit dies funktioniert, muss sich Ihr Konzept normalisieren in:

... && (same-as-impl<T, U> && same-as-impl<U, T>)

in:

... && (is_same_v<T, U> && is_same_v<U, T>)

Betrachten Sie eine &&Einschränkung für Faltausdrücke, um eine andere Einschränkung für Faltausdrücke zu subsumieren, &&wenn die zugrunde liegende Einschränkung die zugrunde liegende Einschränkung der anderen subsumiert. Wenn wir diese Regel hätten, würde Ihr Beispiel funktionieren.

Möglicherweise kann dies in Zukunft hinzugefügt werden. Die Bedenken hinsichtlich der Subsumtionsregeln bestehen jedoch darin, dass Compiler nicht aufgefordert werden müssen, einen vollständigen SAT-Solver zu implementieren, um die Subsumtion von Einschränkungen zu überprüfen. Dieser scheint es nicht viel komplizierter zu machen (wir würden die &&und ||Regeln wirklich nur durch Fold-Ausdrücke hinzufügen ), aber ich habe wirklich keine Ahnung.

Beachten Sie jedoch, dass selbst wenn wir diese Art von Fold-Expression-Subsumtion hätten, are_same<T, U>diese immer noch nicht subsumieren würde std::same_as<T, U>. Es würde nur subsumieren are_same<U, T>. Ich bin mir nicht sicher, ob dies überhaupt möglich wäre.

Barry
quelle
2
Es ist für mich überraschend, dass dies nicht funktioniert. Es ist vernünftig, dass nur Konzepte subsumiert werden können. Meiner Meinung nach würde es jedoch viele Benutzer überraschen (... && C<T>), das Konzept nicht zu subsumieren C<T>.
Metalfox
@metalfox: Nach meiner Lektüre der Normalisierung sollte Ihr Beispiel in Ordnung sein (die Verwendung von Einschränkungen funktioniert explizit über Demo ).
Jarod42
@ Jarod42 Was du geschrieben hast und was Metalfox geschrieben hat, ist nicht dasselbe - der Unterschied ist, wovon Metalfox spricht.
Barry
@ Jarod42 Ja, das funktioniert, weil Konzepte an der Subsumtion von Einschränkungen beteiligt sind und Sie den Fold-Ausdruck (der Konzepte umfasst) in einem einzigen Konzept materialisiert haben. Wie Sie in Ihrer Antwort angegeben haben, werden Falzausdrücke leider nicht in die Konzepte normalisiert, aus denen sie bestehen. Dies funktioniert auch nicht: godbolt.org/z/pjmKxR
metalfox
Ich könnte Constraint_normalization dann falsch verstanden haben : - / Ich verstehe ((fold1<Ts> && ...) && (fold2<Ts> &&...))als Konjunktion von (fold1<Ts> && ...)und (fold2<Ts> && ...)während es atomar ist.
Jarod42
5

Von cppreference.com Constraint_normalization

Die normale Form eines anderen Ausdrucks E ist die atomare Beschränkung, deren Ausdruck E ist und deren Parameterzuordnung die Identitätszuordnung ist. Dies schließt alle Falzausdrücke ein, auch diejenigen, die über && oder || falten Betreiber.

Damit

template <typename... Types>
concept are_same = (... && same_with_others<Types, Types...>);

ist "atomar".

Also in der Tat are_same<U, T>und are_same<T, U>sind nicht gleichwertig.

Ich sehe nicht, wie ich es implementieren soll :-(

Jarod42
quelle