42 votes

Pourquoi les types primitifs et définis par l'utilisateur agissent-ils différemment lorsqu'ils sont renvoyés en tant que "const" à partir d'une fonction?

 #include <iostream>

using namespace std;

template<typename T>
void f(T&&) { cout << "f(T&&)" << endl; }

template<typename T>
void f(const T&&) { cout << "f(const T&&)" << endl; }

struct A {};
const A g1() { return {}; }
const int g2() { return {}; }

int main()
{
    f(g1()); // outputs "f(const T&&)" as expected.
    f(g2()); // outputs "f(T&&)" not as expected.
}
 

La description du problème est incorporée dans le code. Mon compilateur est clang 5.0 .

Je me demande seulement:

Pourquoi C ++ traite-t-il différemment les types intégrés et les types personnalisés?

29voto

Weak to Enuma Elish Points 4261

Je n'ai pas de citation de la norme, mais cppreference confirme mes soupçons:

Un non-classe de tableau non prvalue ne peut pas être de cv qualifiés. (Remarque: un appel de fonction ou de fonte d'expression peut entraîner une prvalue de non-classe de cv qualifiés de type, mais le cv-ci est immédiatement supprimés.)

Le retour de l' const int est juste un type normal int prvalue, et fait de la non-const de surcharge d'un meilleur match que l' const on.

22voto

Pavel Points 1715

Pourquoi ne primitifs et les types définis par l'utilisateur d'agir différemment lorsqu'il est retourné en tant que 'const' partir d'une fonction?

Parce qu' const partie est retiré de types primitifs renvoyées à partir de fonctions. Voici pourquoi:

En C++11 de § 5 Expressions [expr] (p. 84):

8

Chaque fois qu'un glvalue expression apparaît comme un opérande d'un opérateur attend un prvalue pour que opérande, la lvalue-à-rvalue (4.1), tableau de pointeur (4.2), ou de la fonction de pointeur (4.3) standard conversions sont utilisé pour convertir l'expression d'un prvalue. [Note: en raison de cv-qualificatifs sont supprimés à partir du type d'une expression de la non-type de classe lorsque l' l'expression est convertie en prvalue, une lvalue expression de type const int pouvez, par exemple, être utilisés où une prvalue expression de type int est nécessaire. -la note de fin]

Et, de même, à partir d' § 5.2.3 Explicit type conversion (functional notation) [expr.type.conv] (p. 95):

2

L'expression T(), où T est un type simple-rédacteur de devis ou typename-rédacteur de devis pour un non-tableau d'objet de type ou de (éventuellement cv qualifiés) type void, crée un prvalue de la le type,qui est valueinitialized (8.5; pas d'initialisation est faite pour le void() cas). [Note: si T est un non-class type de cv qualifiés, l' cv-qualificatifs sont ignorés lors de la détermination du type de la prvalue (3.10). -la note de fin]

Ce que cela signifie est que, const int prvalue retourné par g2() est effectivement traité comme int.

13voto

songyuanyao Points 2265

Citations de la norme,

§8/6 Expressions [expr]

Si un prvalue a d'abord le type "cv T, où T est un cv-non-qualifié non-classe, non-type de tableau, le type de l'expression est ajusté à T avant tout autre analyse.

et §8/9 Expressions [expr]

(l'emphase est mienne)

Chaque fois qu'un glvalue expression apparaît comme un opérande d'un opérateur qui s'attend à un prvalue pour que opérande, la lvalue-à-rvalue, tableau de pointeur, ou de la fonction de pointeur standard conversions sont utilisé pour convertir l'expression d'un prvalue. [ Note: En Raison cv-qualificatifs sont supprimés à partir du type d'une expression de la non-classe type lorsque l'expression est convertie en prvalue, une lvalue expression de type const int peut, par exemple, être utilisés où une prvalue expression de type int est requis. - la note de fin ]

Donc, pour g2(), int est un non-type de classe, et (la valeur de retour de l') g2() est un prvalue expression, alors const qualificatif est retiré, de sorte que le type de retour n'est pas const int, mais int. C'est pourquoi, f(T&&) est appelé.

2voto

chtz Points 6357

Les réponses précédentes sont parfaitement valables. Je veux juste ajouter une motivation potentielle pour laquelle il peut parfois être utile de retourner des objets const. Dans l'exemple suivant, class A donne un aperçu des données internes de class C , qui dans certains cas ne doivent pas être modifiables (Avertissement: par souci de brièveté, certaines parties essentielles sont omises - également dans ce cas sont probablement des moyens plus faciles pour implémenter ce comportement):

 class A {
    int *data;
    friend class C; // allow C to call private constructor
    A(int* x) : data(x) {}
    static int* clone(int*) {
        return 0; /* should actually clone data, with reference counting, etc */
    }
public:
    // copy constructor of A clones the data
    A(const A& other) : data(clone(other.data)) {}
    // accessor operators:
    const int& operator[](int idx) const { return data[idx]; }
    // allows modifying data
    int& operator[](int idx) { return data[idx]; }
};

class C {
    int* internal_data;
public:
    C() : internal_data(new int[4]) {} // actually, requires proper implementation of destructor, copy-constructor and operator=
    // Making A const prohibits callers of this method to modify internal data of C:
    const A getData() const { return A(internal_data); }
    // returning a non-const A allows modifying internal data:
    A getData() { return A(internal_data); }
};

int main()
{
    C c1;
    const C c2;

    c1.getData()[0] = 1; // ok, modifies value in c1
    int x = c2.getData()[0]; // ok, reads value from c2
    // c2.getData()[0] = 2;  // fails, tries to modify data from c2
    A a = c2.getData(); // ok, calls copy constructor of A
    a[0] = 2; // ok, works on a copy of c2's data
}
 

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