Frage

Ich habe einen seltsamen Unterschied zwischen expliziten und automatischen nachgestellten Rückgabetypen festgestellt.

Im folgenden Code definieren wir eine Struktur, die auf einer Ganzzahl und einer Iter-Funktion basiert und ein Objekt dieses Typs als Argument verwendet.Der Rückgabetyp hängt vom Ergebnis des Aufrufs nach der Dekrementierung des Vorlagenwerts ab.

Um die Instanziierungsschleife zu unterbrechen (so dachte ich zumindest), stelle ich eine Spezialisierung bereit, die einen nicht abhängigen Typ zurückgibt.

Wir haben eine Spielzeughauptplatine, um die Vorlagen zu instanziieren.

Hier ist ein bisschen Code:

template<int i> struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{}));

int main(){
  decltype(iter(Int<10>{})) a;
}

Dieser Code funktioniert nicht sowohl in gcc 4.9 als auch in clang 3.5.Beide lösen eine unendliche Instanziierung aus (sie entsprechen nicht dem speziellen Basisfall).

rec.cpp:11:62: fatal error: recursive template instantiation exceeded maximum depth of 256
template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{}));

Wenn wir nun C++14 verwenden decltype(auto) und wir stellen einen Textkörper für die Vorlage bereit, der genau dasselbe zurückgibt:

template<int i> struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i>
constexpr auto iter(Int<i>) -> decltype(auto) {
  return iter(Int<i-1>{});
}

int main(){
  decltype(iter(Int<10>{})) a;
}

Dies funktioniert jetzt für beide Compiler und verhält sich wie erwartet.

Ich habe verschiedene Möglichkeiten ausprobiert, die Spezialisierung auszudrücken, und sie ein wenig verschoben (um auf den Standort zu achten), aber das hat ihre Selbstverbrennung nicht verhindert ;(

Ich habe auch versucht, den Code mit mehr zu bestreuen decltype Und declval, aber ich bekomme die C++11-Syntax anscheinend nicht zum Laufen.

Könnte jemand den Unterschied zwischen den beiden Syntaxen für die Namenssuche erklären?

War es hilfreich?

Lösung

Dies liegt an der relativen Reihenfolge von Überladungsauflösung, Vorlagenüberladungsauflösung, Instanziierung der Vorlagendeklaration und Vorlage Definition Instanziierung.

Schauen wir uns zunächst den C++11-Fall an.Wenn der Compiler eine Auswertung durchführen muss decltype(iter(Int<0>{})), führt es eine Überlastungsauflösung für den Namen durch iter aufgerufen mit Argumenten prvalue Int<0>.Da sich eine Vorlage im Überladungssatz befindet, wenden wir 14.8.3 an [Temp.über]:

1 - Eine Funktionsvorlage kann entweder durch (nicht zur Vorlage gehörende) Funktionen ihres Namens oder durch (andere) Funktionsvorlagen desselben Namens überladen werden.Wenn ein Aufruf an diesen Namen geschrieben wird (explizit oder implizit unter Verwendung der Bedienernotation), werden für jede Funktionsvorlage für jede Funktionsvorlage die Argumente (14.3) durchgeführt, um die Argumentwerte der Vorlagen zu finden (Argumente der expliziten Vorlagen) (14.8.2) und die Überprüfung explizite Vorlagenargumente (14.3). wenn überhaupt), die mit dieser Funktionsvorlage verwendet werden kann, um eine Funktionsvorlagespezialisierung zu instanziieren, die mit den Anrufargumenten aufgerufen werden kann.[...]

Infolgedessen die Erklärung template<int i> constexpr auto iter(...) -> ... wird instanziiert (14.7.1p10 [temp.inst]) mit i = 0, was die Bewertung von erzwingt decltype(iter(Int<-1>{})) Und ab in den Kaninchenbau der negativen ganzen Zahlen geht es los.

Das spielt keine Rolle constexpr auto iter(Int<0>) -> Int<0> wäre eine bessere Überlastung (durch 13.3.3p1 [over.match.best]), weil wir nie so weit kommen;Der Compiler marschiert fröhlich auf die negative Unendlichkeit zu.

Im Gegensatz dazu wurden mit C++14 die Rückgabetypen 7.1.6.4p12 abgeleitet [dcl.spec.auto] gilt:

12 - Die Ableitung des Rückgabetyps für eine Funktionsvorlage mit einem Platzhalter im deklarierten Typ erfolgt, wenn die Definition instanziiert wird.

Da die Definition instanziiert wird nach Vorlagenüberladungsauflösung (14.7.1p3), die schlechte Vorlage iter<0> wird nie instanziiert;14.8.3p5:

5 – Für die Eingabe der Spezialisierung in eine Reihe von Kandidatenfunktionen ist nur die Signatur einer Funktionsvorlagenspezialisierung erforderlich.Daher ist nur die Funktionsvorlagendeklaration erforderlich, um einen Aufruf aufzulösen, für den eine Vorlagenspezialisierung in Frage kommt.

Die „Signatur“ von iter<0> hier ist (Int<0>) -> decltype(auto), eine Signatur mit a Platzhaltertyp (7.1.6.4).


Vorgeschlagene Problemumgehung:Verwenden Sie SFINAE, um jeden Anrufversuch zu verhindern iter(Int<-1>{}):

template<int i> constexpr auto iter(Int<i>)
  -> decltype(iter(typename std::enable_if<i != 0, Int<i-1>>::type{}));
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        ^^^^^^^

Beachten Sie, dass die SFINAE gehen muss innen Die decltype, und zwar innerhalb des Aufrufs zu iter.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top