4 votes

Est-ce un comportement défini de retourner des références constantes aux objets créés avec la transformation de plages?

Est-ce un comportement défini de combiner une transformation qui crée des objets avec une transformation qui extrait les attributs de ces objets par référence constante ? Le code suivant ne fonctionne pas comme prévu dans Visual Studio 16.10.2, mais semble fonctionner avec gcc 11.1.

#include <array>
#include <iostream>
#include <ranges>
#include <string>

struct A {
    std::string s;
};

int main() {
    auto values = std::array{"a", "b"};

    // transform 1: create objects
    auto objects = values | std::views::transform(
        [](const auto& s) { return A{s}; }
    );

    // transform 2: access object attributes
    auto lambdaReturningConstRef = [](const auto& a) -> const auto& { return a.s; };    
    auto result = objects | std::views::transform(lambdaReturningConstRef);

    for (const auto& s : result) {
        std::cout << s << " ";
    }
}

La sortie attendue est "a b", mais la sortie réelle avec Visual Studio est vide. De plus, dans une version de build Release propre (mais pas en Debug), je reçois un avertissement C4172 concernant le retour de l'adresse d'une variable locale ou temporaire.

Ajouter un getter dans A retournant une référence constante et utiliser std::mem_fn à la place du lambda supprime complètement l'avertissement, mais ne produit toujours pas le résultat attendu. De même pour l'utilisation de &A::s au lieu du lambda.

Supprimer le type de retour explicite du lambda corrige évidemment le problème.

4voto

Barry Points 45207

Je vais écrire le pipeline complet en une fois pour clarifier ce qui se passe :

auto values = std::array{"a", "b"};

auto results = values
             | transform([](const auto& s){ return A{s}; }
             | transform([](const auto& a) -> const auto& { return a.s; });

Donc tout d'abord, nous créons une plage de A prvalue. Ensuite, nous créons une plage de strings lvalue... dans ces A. Mais lorsque vous retournez une référence, vous devez vous assurer qu'elle se réfère à un objet existant, sinon elle sera invalide. Mais à quel A se réfèrent ces string ?

Aux A prvalue. Ceux qui sont ensuite immédiatement détruits. Donc votre référence est invalide.

Autrement dit, vous faites quelque chose comme ceci :

auto __f(const auto& a) -> const auto& { return a.s; }
const auto& s = __f(A{"a"});

Et il est probablement plus clair pourquoi cette dernière formulation vous donne une référence invalide. Le A sort du contexte à la fin de l'expression complète, mais s est une référence à l'intérieur.

Le code des Ranges fait la même chose, juste de manière moins évidente.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X