Ich verwende c ++ 17 mit Boost.hana, um einige Metaprogrammierprogramme zu schreiben. Ein Problem, das mir aufgefallen ist, ist, welche Art von Ausdruck in einem constexpr-Kontext wie static_assert verwendet werden kann. Hier ist ein Beispiel:
#include <boost/hana.hpp>
using namespace boost::hana::literals;
template <typename T>
class X {
public:
T data;
constexpr explicit X(T x) : data(x) {}
constexpr T getData() {
return data;
}
};
int main() {
{ // test1
auto x1 = X(1_c);
static_assert(x1.data == 1_c);
static_assert(x1.getData() == 1_c);
}
{ //test2.1
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
static_assert(x2.data[0_c] == 1_c);
// static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression
}
{ //test2.2
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
auto data = x2.getData();
static_assert(data[0_c] == 1_c);
}
}
Zuerst schreibe ich eine Klasse X mit einem Felddaten und einem Accessor getData () . In der main () ‚s test1 Teil, x1.data und x1.getData () verhalten sich gleich wie ich erwartet hatte. Aber im Test2- Teil static_assert(x2.data[0_c] == 1_c)
verhält sich das Ändern des Arguments in ein Boost :: Hanas-Tupel immer noch gut, static_assert(x2.getData()[0_c] == 1_c)
schlägt jedoch bei der Kompilierung fehl, wobei der Fehler ' Lesen der Nicht-Constexpr-Variablen' x2 'in einem konstanten Ausdruck nicht zulässig ist '. Was weired ist , wenn ich aufgeteilt x2.getData()[0_c]
in auto data = x2.getData();
und static_assert(data[0_c] == 1_c);
kompiliert es wieder gut. Ich würde erwarten, dass sie sich gleich verhalten. Kann jemand helfen zu erklären, warum x2.getData()[0_c]
in static_assert in diesem Beispiel nicht verwendet werden kann?
Zum Reproduzieren: clang ++ 8.0 -I / path / to / hana-1.5.0 / include -std = c ++ 17 Test.cpp
quelle
constexpr
fehlt aufx2
unddata
,const
aufgetData
. godbolt.org/z/ZNL2BKAntworten:
Das Problem ist, dass
boost::hana::tuple
es keinen Kopierkonstruktor gibt.Es hat einen Konstruktor , der wie ein Kopierkonstruktor aussieht :
Da es sich jedoch um eine Vorlage handelt, handelt es sich nicht um einen Kopierkonstruktor .
Da
boost::hana::tuple
keine Kopie Konstruktor wird eine implizit deklarierte und als ausgefallen definiert (es nicht unterdrückt wird , daboost::hana::tuple
nicht hat keine Kopie oder verschieben Bauer oder Zuweisungsoperatoren, weil, Sie ahnen es, sie können nicht Vorlagen sein).Hier sehen wir Implementierungsdivergenzen , die sich im Verhalten des folgenden Programms zeigen:
gcc akzeptiert, während Clang und MSVC ablehnen, aber akzeptieren, wenn die Zeile nicht kommentiert
#1
ist. Das heißt, die Compiler sind sich nicht einig darüber, ob der implizit definierte Kopierkonstruktor einer nicht (direkt) leeren Klasse im Kontext einer konstanten Auswertung verwendet werden darf.Gemäß der Definition des implizit definierten Kopierkonstruktors gibt es keine Möglichkeit, dass sich # 1 von der von
constexpr A(A const&) = default;
gcc unterscheidet . Beachten Sie auch, dass, wenn wir B einen benutzerdefinierten constexpr-Kopierkonstruktor geben, Clang und MSVC erneut akzeptieren, das Problem anscheinend darin besteht, dass diese Compiler die constexpr-Kopierkonstruktierbarkeit rekursiv leerer implizit kopierbarer Klassen nicht verfolgen können. Fehler für MSVC und Clang behoben ( behoben für Clang 11).Beachten Sie, dass die Verwendung von
operator[]
ein roter Hering ist; Die Frage ist, ob die Compiler den AufrufgetData()
(welche KopierkonstrukteT
) in einem Kontext mit konstanter Auswertung zulassen, wie zstatic_assert
.Offensichtlich wäre die ideale Lösung für Boost.Hana, so zu korrigieren
boost::hana::tuple
, dass es tatsächliche Kopier- / Verschiebungskonstruktoren und Kopier- / Verschiebungszuweisungsoperatoren hat. (Dies würde Ihren Anwendungsfall beheben, da der Code vom Benutzer bereitgestellte Kopierkonstruktoren aufruft, die im Kontext einer konstanten Auswertung zulässig sind.) Um dieses ProblemgetData()
zu umgehen , können Sie Hacking in Betracht ziehen , um den Fall eines nicht zustandsbehafteten Falls zu erkennenT
:quelle
test2.2
Teil von Clang akzeptiert wurde? (Ich habe die ursprüngliche Frage bearbeitet und test2 in test2.1 und test2.2 aufgeteilt.) Ich hatte erwartet, dass sie sich gleich verhalten.hana::tuple
, die bei der Rückkehr von auftrittgetData
. In Test2.2 erfolgt die Kopie außerhalb des Kontexts der konstanten Auswertung, sodass Clang damit einverstanden ist.getData()
ist hier nicht erlaubt, aber raus und ein Temperament einzuführen, das dann akzeptiert wird ..Das Problem liegt darin, dass Sie versuchen, einen Laufzeitwert abzurufen und bei der Kompilierung zu testen.
Was Sie tun können, ist, den Ausdruck zur Kompilierungszeit durch a zu zwingen
decltype
und es wird wie ein Zauber funktionieren :).static_assert(decltype(x2.getData()[0_c]){} == 1_c);
Jetzt wird der Ausdruck zur Kompilierungszeit ausgewertet, sodass der Typ zur Kompilierungszeit bekannt ist. Da er auch zur Rechenzeit konstruierbar ist, kann er in einem static_assert verwendet werden
quelle
static_assert(decltype(x2.getData()[0_c]){} == 1_c)
gut funktionieren kann, aber ich möchte das trotzdem speichern,decltype
da dies viel sparen würde. Sie sagen, Sie sagenx2.getData()
, dass ein Laufzeitwert abgerufen wird, damit er nicht in einem static_assert-Ausdruck angezeigt wird. Dann verstehe ich nicht, warum derx1.getData()
Teil in Test1 und die Daten indata[0_c]
Teil Test2 gut funktionieren können. Was sind ihre Unterschiede?Zuallererst fehlt Ihnen das const-Qualifikationsmerkmal in der
getData()
Methode, also sollte es sein:Zumindest aus Standardsicht wird keine Variable als constexpr befördert, wenn sie nicht als constexpr markiert ist.
Beachten Sie, dass dies nicht erforderlich
x1
istX
, da es sich1_c
um einen Typ handelt, der auf hana :: Integral_constant spezialisiert ist, da das Ergebnis ein Typ ohne benutzerdefinierten Kopierkonstruktor ist, der intern keine Daten enthält, sodass eine KopieroperationgetData()
in tatsächlich ein No-Op ist , also Ausdruck:static_assert(x1.getData() == 1_c);
ist in Ordnung, da keine tatsächliche Kopie erstellt wird (und auch kein Zugriff auf einen nicht konstantenthis
Zeiger vonx1
erforderlich ist).Dies ist sehr unterschiedlich für Ihren Container,
hana::tuple
der eine sachliche Kopienkonstruktionhana::tuple
aus Daten enthältx2.data
Feld enthält. Dies erfordert einen sachlichen Zugriff auf Ihrenthis
Zeiger - was im Fall von nicht erforderlichx1
war und auch keine constexpr-Variable war.Dies bedeutet , dass Sie äußern Ihre Absicht falsch mit beiden
x1
undx2
, und es ist notwendig, zumindest fürx2
diese Variablen als constexpr zu markieren. Beachten Sie auch, dass die Verwendung von leerem Tupel, einer grundsätzlich leeren Spezialisierung (keine benutzerdefinierten Kopierkonstruktoren) im Allgemeinenhana::tuple
, nahtlos funktioniert (Abschnitt test3):quelle
x1
ein leerer Typ ist. Jede Instanz vonX
hat ein Datenelement. Dashana::tuple
Enthalten leerer Typen ist selbst leer, da die Optimierung der leeren Basis verwendet wird. Möglicherweise geben Sie dem Kopierkonstruktor die Schuld, weil Clang oder libc ++ möglicherweise etwas Wackeliges tunstd::integral_constant
.constexpr variable 'x3' must be initialized by a constant expression
const this
Zeiger verwenden und ihn leiderx2
im Fall static_assert verwenden. (im Fall von x1 - es ist eine weitere Diskussion :)).hana::integral_constant
der vom Compiler definierte Standardkonstruktorhana::tuple
zwar einen benutzerdefinierten , aber einen benutzerdefinierten Konstruktor hat. Da es eine Spezialisierung für leere Tupel gibt, die keinen Konstruktor hat, funktioniert der gleiche Code für leere Tupel: godbolt.org/z/ZeEVQN