Wie zwischen assert () und static_assert () zu versenden, hängt davon ab, ob im constexpr-Kontext?

8

In C ++ 11 constexpr-Funktionen ist eine zweite Anweisung wie eine assert()nicht möglich. A static_assert()ist in Ordnung, würde aber nicht funktionieren, wenn die Funktion als "normale" Funktion aufgerufen wird. Der Komma-Operator könnte kommen, um wrto zu helfen. Das assert()ist aber hässlich und einige Tools spucken Warnungen darüber aus.

Betrachten Sie einen solchen "Getter", der neben der Behauptung durchaus verständlich ist. Aber ich möchte eine Art Zusicherung für die Laufzeit und die Kompilierungszeit behalten, kann aber nicht einfach überladen, abhängig vom Kontext 'constexpr'.

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement( int idx ) const
  {
    ASSERT( idx < Size ); // a no-go for constexpr funcs in c++11
    // not possible, even in constexpr calls as being pointed out, but what I would like:
    static_assert( idx < Size, "out-of-bounds" );
    return m_vals[idx];
  }
};

Nebenbedingungen: C ++ 11, kein Heap, keine Ausnahmen, keine Compilerspezifikationen.

Beachten Sie, wie Kommentatoren betonten (danke!), static_assertDas Argument ist nicht möglich (wäre aber nett). Der Compiler hat mir in dieser Situation einen anderen Fehler beim Zugriff außerhalb der Grenzen gemeldet.

Borph
quelle
" aber ist hässlich " Nun, der Komma-Operator mag hässlich sein, aber er erledigt den Job in C ++ 11. Die ausgefallene Lösung wäre, auf C ++ 14 umzusteigen. :)
Eichel
" Einige Tools spucken Warnungen aus " Welche Tools und welche Warnungen?
Eichel
Der Komma-Operator war früher meine Lösung, aber a) wird durch statische Code-Analyse wie qacpp (Codierungsrichtlinien) gewarnt und b) führte zu einem seltsamen Syntaxfehler bei einem nachgeschalteten Projekt (nicht verstanden, vermutet ein benutzerdefiniertes Assert-Makro) . Nun, ich versuche es jetzt nur zu vermeiden, stimme aber zu, dass es den Job gemacht hat.
Borph
2
@Borph Nein, Sie können static_assertabhängig überhaupt nicht verwenden idx. Sie können nur dann einen falschen Wert diagnostizieren, idxwenn die Funktion in einem Kontext verwendet wird, der einen konstanten Ausdruck erfordert, indem Sie die Auswertung eines Konstrukts erzwingen, das es nicht zu einem konstanten Ausdruck macht. Außerhalb eines solchen Kontexts können Sie den Wert zur Kompilierungszeit niemals überprüfen.
Walnuss

Antworten:

2

Etwas wie

void assert_impl() { assert(false); } // Replace body with own implementation

#ifdef NDEBUG // Replace with own conditional
#define my_assert(condition) ((void)0)
#else
#define my_assert(condition) ((condition) ? (void()) : (assert_impl(), void()))
#endif

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement( int idx ) const
  {
    return my_assert(idx < Size), m_vals[idx];
  }
};

Es wird ein Kompilierungszeitfehler bei einem Assertionsfehler ausgegeben, wenn es in einem Kontext verwendet wird , der einen konstanten Ausdruck erfordert (weil es eine Nichtfunktion aufruft constexpr).

Andernfalls schlägt es zur Laufzeit mit einem Aufruf von assert(oder Ihrem Analog) fehl .

Soweit ich weiß, ist dies das Beste, was Sie tun können. Es gibt keine Möglichkeit, den Wert von idxzu verwenden, um eine Überprüfung zur Kompilierungszeit außerhalb des Kontexts zu erzwingen , der konstante Ausdrücke erfordert .

Die Syntax des Kommaoperators ist nicht gut, aber die C ++ 11- constexprFunktionen sind sehr eingeschränkt.

Wie Sie bereits bemerkt haben, wird undefiniertes Verhalten natürlich trotzdem diagnostiziert, wenn die Funktion in einem Kontext verwendet wird, der einen konstanten Ausdruck erfordert.

Wenn Sie wissen, dass assert(oder Ihr Analogon) sich nicht auf etwas erweitert, das in einem konstanten Ausdruck verboten ist, wenn die Bedingung ausgewertet wird, truesondern wenn dies ausgewertet wird false, können Sie es direkt verwenden, anstatt die von my_assertmir erstellte Indirektion zu überspringen in meinem Code.

Nussbaum
quelle
1
Könnte mir bitte jemand angesichts der Abstimmungen erklären, wo meine Antwort falsch ist?
Walnuss
Diese und @ecatmur Lösung sind ähnlich, ich kann nur eine Antwort wählen. Ihre ist unkompliziert. Eine Bemerkung: Warum (void)0in dem NDEBUGFall und void()in der anderen? Oder ist es wirklich dasselbe?
Borph
@Borph (void)0ist ein No-Op, es wird zu nichts kompiliert (was Sie wollen, wenn NDEBUGes definiert ist), während Sie das benötigen, void()damit der zweite und dritte Operand des bedingten Operators den gleichen Typ haben void.
Bob__
@ Morph Ich denke, (void)0wäre in jedem Fall auch in Ordnung. Ich habe es gerade im ersten Fall ersetzt, da void()es je nach Kontext auch als Funktionstyp ohne Parameter und ohne Rückgabetyp analysiert werden kann. In den Unterausdrücken im zweiten Fall kann dies nicht analysiert werden.
Walnuss
3

Besser als ein Komma-Ausdruck können Sie eine ternäre Bedingung verwenden. Der erste Operand ist Ihr Assertionsprädikat, der zweite Operand ist Ihr Erfolgsausdruck. Da der dritte Operand ein beliebiger Ausdruck sein kann - auch einer, der in einem konstanten C ++ 11-Kontext nicht verwendet werden kann -, können Sie mit einem Lambda die ASSERTEinrichtung Ihrer Bibliothek aufrufen :

#define ASSERT_EXPR(pred, success)    \
    ((pred) ?                         \
     (success) :                      \
     [&]() -> decltype((success))     \
     {                                \
         ASSERT(false && (pred));     \
         struct nxg { nxg() {} } nxg; \
         return (success);            \
     }())

Erklärung des Lambda-Körpers:

  • ASSERT(false && (pred)) soll sicherstellen, dass Ihre Assertionsmaschinerie mit einem geeigneten Ausdruck (zur Stringifizierung) aufgerufen wird.
  • struct nxg { nxg() {} } nxgdient der Zukunftssicherheit, um sicherzustellen, dass beim Kompilieren in C ++ 17 oder höher NDEBUGdas Lambda immer noch nicht vorhanden ist constexprund die Behauptung daher im Kontext der Konstantenbewertung erzwungen wird.
  • return (success)gibt es aus zwei Gründen: um sicherzustellen, dass der zweite und der dritte Operand den gleichen Typ haben, und damit NDEBUGder successAusdruck unabhängig davon zurückgegeben wird , ob Ihre Bibliothek dies respektiert pred. ( predwird ausgewertet , aber Sie hoffen, dass Assertionsprädikate billig zu bewerten sind und keine Nebenwirkungen haben.)

Anwendungsbeispiel:

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr int getElement( int idx ) const
  {
    return ASSERT_EXPR(idx < Size, m_vals[idx]);
  }
};

constexpr int I = Array<2>{1, 2}.getElement(1); // OK
constexpr int J = Array<2>{1, 2}.getElement(3); // fails
ecatmur
quelle
Ah! @walnut und eine nicht initialisierte Variable sind möglicherweise später zulässig, wenn nicht darauf zugegriffen wird. Ich werde versuchen, eine bessere Wache zu finden. Vielen Dank!
ecatmur
Vorschlag: s / [&]/ [&] -> decltype((success))um Referenzen zu erhalten.
LF
@LF guter Punkt, danke!
ecatmur
Sie haben Recht, dass die predBewertung billig sein sollte, aber sie sind nicht immer. Als allgemeines ASSERT_EXPRMakro würde ich es also nicht empfehlen. Ich mache manchmal teure Anrufe in einer Behauptung selbst (z. B. um Invarianten zu überprüfen).
Borph
1
@Borph Ich gehe davon aus, dass auch wenn Sie NDEBUGzum Aktivieren von Laufzeit- Asserts aktiviert sind, die Assets zur Kompilierungszeit überprüft werden sollen. constexprDies kann sichergestellt werden, indem der Body des Fehlerfalls Lambda nicht verwendet wird. Dies hat jedoch die Kosten für die Auswertung und das Verwerfen des Prädikats zur Laufzeit in NDEBUG. Andernfalls könnten Sie das Makro unter NDEBUGeinfach definieren return (success);.
ecatmur
2

static_assertkann hier nicht verwendet werden. Das Argument für eine constexprFunktion ist in einem Konstantenausdruck nicht zulässig. Daher gibt es unter den angegebenen Bedingungen keine Lösung für Ihr Problem .

Wir können das Problem jedoch lösen, indem wir zwei Einschränkungen biegen

  1. nicht verwenden static_assert(verwenden Sie stattdessen andere Methoden, um eine Diagnose zur Kompilierungszeit zu erstellen) und

  2. Ignorieren Sie, dass der Komma-Operator "hässlich ist und einige Tools Warnungen darüber ausspucken". (Das Zeigen seiner Hässlichkeit ist eine unglückliche Folge der strengen Anforderungen der C ++ 11- constexprFunktionen.)

Dann können wir ein normales verwenden assert:

template <int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement(int idx) const
  {
    return assert(idx < Size), m_vals[idx];
  }
};

In einem konstanten Auswertungskontext wird dies einen Compilerfehler wie ausgeben error: call to non-'constexpr' function 'void __assert_fail(const char*, const char*, unsigned int, const char*)'.

LF
quelle
-2

Dies funktioniert für mich bei den meisten Compilern: https://godbolt.org/z/4nT2ub

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement(int idx) const
  {
    assert(idx < Size);
    return m_vals[idx];
  }
};

Jetzt static_assertist veraltet, da constexpres kein undefiniertes Verhalten enthalten kann. Wenn Sie also den Array-Index-Compiler sprengen, meldet er den richtigen Fehler. Siehe hier .

Problem ist assert. Es ist ein Makro, dessen Implementierung nicht definiert ist. Wenn der Compiler eine Funktion verwendet, die keine ist, schlägt constexprsie fehl, aber wie Sie sehen können, haben 3 große Compiler damit kein Problem.

Marek R.
quelle
1
Dies funktioniert nicht in C ++ 11 (eine der Anforderungen von OP). Die Compiler verwenden wahrscheinlich standardmäßig C ++ 14 oder höher.
Walnuss
mit C++11dieser nicht auf gcc nur godbolt.org/z/DB2zL3
Marek R
Schauen Sie sich die Warnmeldungen an. MSVC akzeptiert das /std:c++11Flag überhaupt nicht und Clang lässt den Code zu, obwohl C ++ 14 erforderlich ist. Add -pedantic-errorsand Clang gibt die richtigen Fehler aus, die ein reiner C ++ 11-Compiler geben würde.
Walnuss
-3

c ++ 11 kann nicht so sein. Entweder idxkonstant oder nein
wäre schön, wenn jeweils eine Funktion vorhanden wäre.
Es könnte so sein, wenn eine Funktion erzwungen wird

template<int Size>
struct Array {
    int m_vals[Size];
    constexpr const  int& getElement( int idx ) const       
    {
    if constexpr(is_same_v<decltype(idx), const int>)
        static_assert( idx < Size, "out-of-bounds" ); // a no-go for non-constexpr calls
    else 
        assert( idx < Size ); // a no-go for constexpr funcs in c++11

    return m_vals[idx];
    }
};

int main() {                // E.g. up to next //

        Array<7> M;  
        int i=M.getElement(1);
}                           //
nonock
quelle
1
Ich bin nicht sicher, ob dies eine Antwort auf die Frage sein soll oder nicht, aber Ihr Code ist in keiner C ++ - Version gültig. Das Problem ist nicht , ob oder nicht idxist const. Wenn dies nur ein C ++ - Standardvorschlag ist, sehe ich im Antwortbereich nicht, wie er dazu gehört.
Walnuss