std :: pair <auto, auto> Rückgabetyp

16

Ich habe mit autoin herumgespielt std::pair. Im folgenden Code soll die Funktion feinen std::pairTyp zurückgeben, der von einem Vorlagenparameter abhängt.

Ein Arbeitsbeispiel:

BEISPIEL 1

template <unsigned S>
auto f()
{
    if constexpr (S == 1)
        return std::pair{1, 2}; // pair of ints
    else if constexpr (S == 2)
        return std::pair{1.0, 2.0}; // pair of doubles
    else
        return std::pair{0.0f, 0.0f}; // pair of floats
}

Dies funktioniert mit gcc 9.2, gcc 10.0, clang 9.0 und clang 10.0.

Als nächstes wollte ich den Rückgabetyp std::pairaus Gründen der Übersichtlichkeit explizit schreiben :

BEISPIEL 2

template <unsigned S>
std::pair<auto, auto> f()
{
    if constexpr (S == 1)
        return {1, 2};
    /* ... */
}

Sowohl gcc 9.2 / 10.0 als auch clang 9.0 / 10.0 konnten dies nicht kompilieren.

gcc 9.2

error: invalid use of 'auto'
error: template argument 1 is invalid // first argument (auto) of std::pair
error: template argument 2 is invalid // second argument (auto) of std::pair
error: cannot convert '<brace-enclosed initializer list>' to 'int' in return

Nach der letzten Fehlermeldung scheint gcc 9.2 zu glauben, dass dies std::pair<auto, auto>eine ist int. Wie kann das erklärt werden?

gcc 10.0

error: returning initializer list

Dieser Fehler ist verständlich, ich habe jedoch erwartet, dass der Konstruktor von std::pairaufgerufen wird, oder fehlt hier etwas?

klirren 9.0 und 10.0

'auto' not allowed in template argument
excess elements in scalar initializer
no matching function for call to 'f'

Ok, Clang mag nichts davon. Aus der zweiten Fehlermeldung geht hervor, dass clang auch glaubt, dass der Rückgabetyp ist int.

Um den beim Kompilieren mit gcc 10.0 erhaltenen Fehler zu beheben, habe ich mich entschlossen, std::pairexplizit Folgendes zurückzugeben:

BEISPIEL 3

template <unsigned S>
std::pair<auto, auto> f()
{
    if constexpr (S == 1)
        return std::pair{1, 2};
    /* ... */
}

klirren 9.0 und 10.0

Wie zuvor, jedoch mit einem zusätzlichen:

no viable conversion from returned value of type 'std::pair<int, int>' to function return type 'int'

Hier denkt Clang immer noch, wir kehren zurück int?

gcc 9.2

Das Gleiche wie vorher.

gcc 10.0

Es klappt!

Ich denke, einige Funktionen müssen noch implementiert werden, oder gibt es in einer der oben beschriebenen Situationen einen Compiler, der richtig und der andere falsch ist? Meiner Meinung nach sollte Beispiel 2 funktionieren. Oder sollte es nicht?

mfnx
quelle

Antworten:

23

Die Syntax:

std::pair<auto, auto> f() { return std::pair(1, 2); }
~~~~~~~~~~~~~~~~~~~~~

War Teil des ursprünglichen Concepts TS, wurde aber nicht in den Concepts-Vorschlag aufgenommen, der Teil von C ++ 20 ist. Daher sind die einzigen Platzhaltertypen in C ++ 20 auto(und Variationen davon wie auto**) decltype(auto)und eingeschränkte Platzhalter ( Concept autound Variationen davon). Diese Art von verschachteltem Platzhaltertyp wäre sehr nützlich, ist jedoch nicht Teil von C ++ 20, sodass die Funktionsdeklaration fehlerhaft ist.

Jetzt erlaubt gcc dies, weil gcc die Concepts TS implementiert hat und ich denke, sie haben beschlossen, diese Funktion beizubehalten. clang hat den TS nie implementiert, also nicht.

So oder so:

std::pair<auto, auto> f() { return {1, 2}; }

Wäre immer schlecht geformt. Die Bedeutung der Syntax besteht darin, dass wir den Rückgabetyp ableiten und dann verlangen, dass er pair<T, U>für einige Typen Tund übereinstimmt U. Wir versuchen im Grunde, die erfundene Funktion aufzurufen:

template <typename T, typename U>
void __f(std::pair<T, U>);

__f({1, 2}); // this must succeed

Sie können jedoch keinen Typ daraus ableiten {1, 2}- eine Klammer-Init-Liste hat keinen Typ. Vielleicht sollte dies untersucht werden (da es zumindest in einem einfachen Fall wie diesem leicht zu verstehen ist), aber es wurde nie erlaubt. Es ist also so oder so richtig, es abzulehnen.

Zuletzt:

gcc 9.2 scheint zu glauben, dass dies ein std::pair<auto, auto>ist int. Wie kann das erklärt werden?

Aus irgendeinem Grund (wahrscheinlich aufgrund unseres impliziten C-Erbes int) wird gcc, wenn es einen Typ nicht erkennt oder versteht, nur intals Platzhalter in Fehlermeldungen verwendet. Das ist super verwirrend, denn offensichtlich ist es gcc, das sich ausgedacht hat intund nicht der Quellcode. Aber so ist es nun mal.

Barry
quelle
Die "Klammer-Init-Liste hat kein Typargument" ist mir nicht klar. std :: pair <int, int> f () {return {1,2}; } funktioniert und {1,2} hat keinen Typ (es ruft den Konstruktor von std :: pair <int, int> auf, wie ich es verstehe). Vielleicht kann der Compiler mit <auto, auto> die Typen 1 und 2 in der Initialisierungsliste {1, 2} nicht ableiten?
mfnx
@mfnx hat nicht kein Typ Argument , nicht nur keinen Typ. geschweifte Init-Listen können nur in bestimmten Situationen verwendet werden - beispielsweise beim Initialisieren eines bekannten Typs. Sie können jedoch nicht zum Abzug verwendet werden, da sie keinen Typ haben. Außer auto x = {1, 2};funktioniert, aber nur wenn alle Typen gleich sind.
Barry
2
Die meisten Compiler versuchen nicht nur beim ersten Fehler anzuhalten, sondern versuchen, ihn zu beheben, damit sie zusätzliche Fehler melden können. Dies bedeutet im Allgemeinen die Annahme, dass alles, was nicht analysierbar ist, ein int. Es ist nicht so, dass dies intein Platzhalter in Fehlermeldungen ist. Der Compiler glaubt wirklich, dass es ein ist int. (Um dies klarer zu machen, hätte gcc wahrscheinlich irgendwann "int annehmen" sagen sollen.)
Raymond Chen
2
Beachten Sie, dass ein weiterer möglicher Weg zur Erweiterung darin besteht, das Abziehen von Klassenvorlagenargumenten für Rückgabetypen zuzulassen, da dies std::pair __f{1,2};funktioniert.
Davis Herring
2
@DavisHerring Ich würde eigentlich keine std::optional f() { return 4; }Arbeit haben wollen .
Barry