it-swarm.com.de

Funktionsargument als Teil eines konstanten Ausdrucks verwenden - gcc vs clang

Betrachten Sie den folgenden Codeausschnitt:

template <bool> struct B { };

template <typename T>
constexpr bool pred(T t) { return true; } 

template <typename T>
auto f(T t) -> decltype(B<pred(t)>{})
{
}
  • clang ++ (trunk) kompiliert den Code

  • g ++ (trunk) schlägt die Kompilierung mit dem folgenden Fehler fehl:

    src:7:34: error: template argument 1 is invalid
    auto f(T t) -> decltype(B<pred(t)>{})
                                    ^
    
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:25: error: invalid template-id
    auto f(T t) -> decltype(B<pred(t)>{})
                            ^
    
    src:7:36: error: class template argument deduction failed:
    auto f(T t) -> decltype(B<pred(t)>{})
                                        ^
    
    src:7:36: error: no matching function for call to 'B()'
    src:1:24: note: candidate: 'template<bool <anonymous> > B()-> B<<anonymous> >'
    template <bool> struct B { };
                            ^
    
    src:1:24: note:   template argument deduction/substitution failed:
    src:7:36: note:   couldn't deduce template parameter '<anonymous>'
    auto f(T t) -> decltype(B<pred(t)>{})
                                        ^
    

    Live-Beispiel auf godbolt.org


Obwohl die Diagnose von g ++ irreführend ist, gehe ich davon aus, dass das Problem hier ist, dass t kein konstanter Ausdruck ist. Code ändern in ...

decltype(B<pred(T{})>{})

... behebt den Kompilierungsfehler in g ++: live Beispiel auf godbolt.org


Welcher Compiler verhält sich hier richtig? 

24
Vittorio Romeo

GCC ist falsch. Es gibt keine Regel, die die Verwendung der Parameter einer Funktion in einem konstanten Ausdruck auf diese Weise verhindert.

Sie können jedoch den Wert des Parameters nicht in einem solchen Kontext verwenden, und die Menge der Typen T, für die f aufgerufen werden kann, ist ziemlich eingeschränkt. Um zu sehen, warum, müssen wir berücksichtigen, welche Konstrukte bei der Auswertung des Ausdrucks pred(t) ausgewertet werden:

// parameters renamed for clarity
template <typename U>
constexpr bool pred(U u) { return true; } 

template <typename T>
auto f(T t) -> decltype(B<pred(t)>{});

Die Evaluierungssemantik für den Aufruf pred(t) lautet wie folgt:

  1. copy-initialize preds Parameter u von fs Parameter t
  2. werte den Rumpf von pred aus, wodurch auf einfache Weise ein bool-Wert true erstellt wird
  3. zerstöre u

f ist also nur für Typen T aufrufbar, für die das Obige nur Konstrukte umfasst, die während der ständigen Auswertung gültig sind (siehe [expr.const] p2 für die Regeln). Die Anforderungen sind:

  • T muss ein Literaltyp sein
  • die Kopierinitialisierung von u aus t muss ein konstanter Ausdruck sein und darf insbesondere keine Konvertierung von Lvalue in rvalue für jedes Member von t durchführen (da deren Werte nicht bekannt sind) und kein Referenzmitglied von t benannt werden darf.

In der Praxis bedeutet dies, dass f aufrufbar ist, wenn T ein leerer Klassentyp mit einem voreingestellten Kopierkonstruktor ist oder wenn T ein Klassentyp ist, dessen Kopierkonstruktor constexpr ist und keine Mitglieder seines Arguments liest, oder (seltsam), wenn T ist std::nullptr_t (obwohl clang aktuell den nullptr_t Fall falsch bekommt).

3
Richard Smith

t ist kein constexpr-Wert, das bedeutet, pred(t) ist auch nicht constexpr .. __ Sie können ihn nicht in B<pred(t)> verwenden, da dies constexpr benötigt.

Diese Version wird korrekt kompiliert:

template <bool> struct B { };

template <typename T>
constexpr bool pred(T t) { return true; } 

template <typename T, T t>
auto f() -> decltype(B<pred(t)>{})
{
}

https://godbolt.org/g/ydbj1X

Ein anderer gültiger Code ist:

template <typename T>
auto f(T t) -> decltype(pred(t))
{
}

Dies liegt daran, dass Sie pred(t) nicht auswerten, sondern nur Typinformationen erhalten B<pred(t)> muss pred(t) ausgewertet werden. Andernfalls erhalten Sie B<true> oder B<false>. Für jeden normalen Wert können Sie dies nicht.

std::integral_constant<int, 0>{} kann im Clang-Fall funktionieren, liegt wahrscheinlich daran, dass sein Wert als Teil von type eingebaut wird und immer derselbe ist. Wenn wir den Code etwas ändern:

template <typename T>
auto f(T t) -> decltype(B<pred(decltype(t){})>{})
{
    return {};
}

sowohl Clang als auch GCC kompilieren es, falls std::integral_constant, sowohl t als auch decltype(t){} immer denselben Wert haben.

0
Yankes

Der Compiler erwartet in diesem Kontext einen Parameter, da er den vollständigen (überlasteten) Funktionstyp auswerten muss. Bei der Implementierung von pred würde jeder Wert an diesem Standort funktionieren. Hier bindet er den Vorlagentyp des Parameters f an das Argument . Der g ++ - Compiler scheint eine vereinfachende Annahme zu machen, dass eine constexpr-Funktion der Vorlage irgendwie von Parametern geändert wird, es sei denn, sie sind ebenfalls const, was Sie auch tun demonstriert, und clang stimmt zu, ist nicht notwendigerweise der Fall.

Es kommt darauf an, wie tief der Compiler die Funktion innerhalb der Funktionsimplementierung als Nicht-Konstante markiert, da er nicht zum konstanten Beitrag zum Rückgabewert gehört.

Dann stellt sich die Frage, ob die Funktion instanziiert ist und der Compiler den Code tatsächlich kompilieren muss, um das Template-Parsing durchzuführen, was zumindest bei g ++ eine andere Kompilierungsstufe zu sein scheint.

Dann ging ich zum Standard und sie erlauben dem Compiler-Schreiber, genau diese vereinfachende Annahme zu treffen, und die Instantiierung der Template-Funktion sollte nur für f<const T> oder f <const T&> funktionieren.

constexpr`-Funktionen müssen Folgendes aufweisen: Jeder seiner Parameter muss .__ sein. LiteralType

Der Template-Code sollte also kompiliert werden, wenn er mit einem Nicht-Konstanten T instanziiert wird.

0
dex black