std :: pair <auto, auto> type de retour

16

Je jouais avec autoen std::pair. Dans le code ci-dessous, la fonction fest censée retourner un std::pairtype qui dépend d'un paramètre de modèle.

Un exemple pratique:

EXEMPLE 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
}

Cela fonctionne avec gcc 9.2, gcc 10.0, clang 9.0 et clang 10.0.

Ensuite, je voulais écrire explicitement le type de retour en tant que std::pairpour des raisons de clarté:

EXEMPLE 2

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

Gcc 9.2 / 10.0 et clang 9.0 / 10.0 n'ont pas réussi à le compiler.

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

D'après le dernier message d'erreur, gcc 9.2 semble croire que std::pair<auto, auto>c'est un int. Comment cela peut-il être expliqué?

gcc 10.0

error: returning initializer list

Cette erreur est compréhensible, cependant, je m'attendais à ce que le constructeur de std::pairsoit invoqué, ou y a-t-il quelque chose qui me manque ici?

clang 9.0 et 10.0

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

Ok, clang n'aime rien de tout ça. D'après le deuxième message d'erreur, il semble que clang pense également que le type de retour est int.

Enfin, pour corriger l'erreur obtenue lors de la compilation avec gcc 10.0, j'ai décidé de retourner std::pairexplicitement un :

EXEMPLE 3

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

clang 9.0 et 10.0

Comme avant, mais avec en plus:

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

Ici, Clang pense toujours que nous retournons un int?

gcc 9.2

Pareil qu'avant.

gcc 10.0

Ça marche!

Je suppose que certaines fonctionnalités doivent encore être implémentées, ou dans l'une des situations décrites ci-dessus, existe-t-il un compilateur qui est bon et l'autre mal? À mon avis, l'exemple 2 devrait fonctionner. Ou non?

mfnx
la source

Réponses:

23

La syntaxe:

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

Faisait partie de la version originale de Concepts TS mais n'était pas incluse dans la proposition Concepts qui fait partie de C ++ 20. En tant que tels, les seuls types d'espace réservé en C ++ 20 sont auto(et leurs variantes similaires auto**) decltype(auto), et les espaces réservés contraints ( Concept autoet leurs variantes). Ce type de type d'espace réservé imbriqué serait très utile, mais ne fait pas partie de C ++ 20, de sorte que la déclaration de fonction est mal formée.

Maintenant, gcc le permet car gcc a implémenté le Concepts TS et je suppose qu'ils ont décidé de conserver cette fonctionnalité. clang n'a jamais implémenté le TS, donc ce n'est pas le cas.

De toute façon, ceci:

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

Serait toujours mal formé. La signification de la syntaxe est que nous déduisons le type de retour et que nous demandons ensuite qu'il corresponde pair<T, U>à certains types Tet U. Nous essayons essentiellement d'invoquer la fonction inventée:

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

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

Mais vous ne pouvez pas en déduire un type {1, 2}- une liste d'initiation contreventée n'a pas de type. C'est peut-être quelque chose qui devrait être exploré (car c'est facile à comprendre au moins dans un cas simple comme celui-ci), mais cela n'a jamais été autorisé. Donc, le rejeter est correct dans les deux cas.

Enfin:

gcc 9.2 semble croire que std::pair<auto, auto>c'est un int. Comment cela peut-il être expliqué?

Pour une raison quelconque (probablement en raison de notre héritage C avec implicite int), lorsque gcc ne reconnaît pas ou ne comprend pas un type, il utilise simplement intcomme espace réservé dans les messages d'erreur. C'est super déroutant, car il est évident que c'est gcc qui est venu intet non le code source. Mais c'est comme ça.

Barry
la source
La «liste d'initiation contreventée n'a pas d'argument de type» n'est pas claire pour moi. std :: pair <int, int> f () {return {1,2}; } fonctionne et {1,2} n'a pas de type (il invoque le constructeur de std :: pair <int, int> tel que je le comprends). Peut-être qu'avec <auto, auto>, le compilateur ne peut pas déduire les types de 1 et 2 dans la liste d'initialisation {1, 2}?
mfnx
@mfnx Non n'a pas d' argument de type , n'a tout simplement pas de type. les listes d'init entre parenthèses ne peuvent être utilisées que dans certaines situations - comme l'initialisation d'un type connu. Mais ils ne peuvent pas être utilisés en déduction - car ils n'ont pas de type. Sauf auto x = {1, 2};travaux, mais uniquement si tous les types sont identiques.
Barry
2
La plupart des compilateurs, au lieu de simplement s'arrêter à la première erreur, tentent de s'en remettre pour pouvoir signaler des erreurs supplémentaires. Cela signifie généralement supposer que tout ce qui n'est pas analysable est un int. Ce n'est pas que intc'est un espace réservé dans les messages d'erreur; le compilateur pense vraiment que c'est un int. (Pour rendre cela plus clair, gcc aurait probablement dû dire "en supposant int" à un moment donné.)
Raymond Chen
2
Notez qu'une autre possibilité d'extension serait de permettre la déduction d'arguments de modèle de classe pour les types de retour, car cela std::pair __f{1,2};fonctionne.
Davis Herring
2
@DavisHerring Je ne voudrais pas vraiment avoir du std::optional f() { return 4; }travail.
Barry