为什么原语类型和用户定义类型在从函数返回“const”时行为不同?

时间:2022-09-25 12:44:43
#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.}

The issue description is embedded in the code. My compiler is clang 5.0.

问题描述嵌入到代码中。我的编译器是clang 5.0。

I just wonder:

我只是想知道:

Why does C++ treat built-in types and custom types differently in such a case?

为什么c++在这种情况下会对内置类型和自定义类型有不同的处理?

4 个解决方案

#1


30  

I don't have a quote from the standard, but cppreference confirms my suspicions:

我没有标准的报价,但是cppreference证实了我的怀疑:

A non-class non-array prvalue cannot be cv-qualified. (Note: a function call or cast expression may result in a prvalue of non-class cv-qualified type, but the cv-qualifier is immediately stripped out.)

非类非数组prvalue不能被cv限定。(注意:函数调用或转换表达式可能会导致非类cv限定类型的prvalue,但会立即删除cv限定符。)

The returned const int is just a normal int prvalue, and makes the non-const overload a better match than the const one.

返回的const int只是一个普通的int prvalue,并且使得非const重载比const更匹配。

#2


22  

Why do primitive and user-defined types act differently when returned as 'const' from a function?

为什么原语类型和用户定义类型在从函数返回“const”时行为不同?

Because const part is removed from primitive types returned from functions. Here's why:

因为const部分从函数返回的原始类型中删除。原因如下:

In C++11 from § 5 Expressions [expr] (p. 84):

在c++中11从§5表达式[expr](p。84):

8

8

Whenever a glvalue expression appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue (4.1), array-to-pointer (4.2), or function-to-pointer (4.3) standard conversions are applied to convert the expression to a prvalue. [Note: because cv-qualifiers are removed from the type of an expression of non-class type when the expression is converted to a prvalue, an lvalue expression of type const int can, for example, be used where a prvalue expression of type int is required. —end note]

每当glvalue表达式作为运算符的操作数出现时,就会应用lvalue-to-rvalue(4.1)、arrayto -pointer(4.2)或functionto -pointer(4.3)标准转换将表达式转换为prvalue。[注意:由于在将表达式转换为prvalue时,从非类类型的表达式类型中删除了cv-限定符,例如,如果需要类型int的prvalue表达式,则可以使用类型const int的lvalue表达式。端注)

And similarly from § 5.2.3 Explicit type conversion (functional notation) [expr.type.conv] (p. 95):

同样从§5.2.3显式类型转换(功能符号)[expr.type。conv](p。95):

2

2

The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates a prvalue of the specified type,which is valueinitialized (8.5; no initialization is done for the void() case). [Note: if T is a non-class type that is cv-qualified, the cv-qualifiers are ignored when determining the type of the resulting prvalue (3.10). —end note]

表达式T(),其中T是一个简单类型说明符或用于非数组完整对象类型或(可能是cv限定的)void类型的typenam -说明符,创建指定类型的prvalue,它是valueinitialized (8.5;对void()情况不进行初始化。[注意:如果T是cv限定的非类类型,则在确定结果prvalue的类型时忽略cv限定符(3.10)。端注)

What that means is that const int prvalue returned by g2() is effectively treated as int.

这意味着g2()返回的const int prvalue被有效地视为int。

#3


13  

Quotes from the standard,

引用的标准,

§8/6 Expressions [expr]

§8/6表达式(expr)

If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

如果prvalue最初具有“cv T”类型,其中T是cv不合格的非类、非数组类型,在进行进一步分析之前,表达式的类型将调整为T。

and §8/9 Expressions [expr]

和§8/9表达式(expr)

(emphasis mine)

(强调我的)

Whenever a glvalue expression appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue, array-to-pointer, or function-to-pointer standard conversions are applied to convert the expression to a prvalue. [ Note: Because cv-qualifiers are removed from the type of an expression of non-class type when the expression is converted to a prvalue, an lvalue expression of type const int can, for example, be used where a prvalue expression of type int is required. — end note ]

每当glvalue表达式作为运算符的操作数出现时,就应用lvalue-to-rvalue、arrayto -pointer或function-to-pointer标准转换将表达式转换为prvalue。[注意:由于在将表达式转换为prvalue时,从非类类型的表达式类型中删除了cv-限定符,例如,如果需要类型int的prvalue表达式,则可以使用类型const int的lvalue表达式。——结束注意)

So for g2(), int is a non-class type, and (the return value of) g2() is a prvalue expression, then const qualifier is removed, so the return type is not const int, but int. That's why f(T&&) is called.

因此,对于g2(), int是一个非类类型,并且()g2()的返回值是一个prvalue表达式,那么const限定符被删除,因此返回类型不是const int,而是int.这就是为什么调用f(T&&)。

#4


2  

The previous answers are perfectly valid. I just want to add a potential motivation why it may sometimes be useful to return const objects. In the following example, class A gives a view on internal data from class C, which in some cases shall not be modifyable (Disclaimer, for brevity some essential parts are left out -- also there are likely easier ways to implement this behavior):

前面的答案是完全正确的。我只是想添加一个潜在的动机,为什么返回const对象有时是有用的。在下面的例子中,类A给出了关于类C的内部数据的视图,在某些情况下这些数据是不可修改的(声明,为了简洁性,省略了一些重要的部分——也有可能有更简单的方法来实现这种行为):

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}

#1


30  

I don't have a quote from the standard, but cppreference confirms my suspicions:

我没有标准的报价,但是cppreference证实了我的怀疑:

A non-class non-array prvalue cannot be cv-qualified. (Note: a function call or cast expression may result in a prvalue of non-class cv-qualified type, but the cv-qualifier is immediately stripped out.)

非类非数组prvalue不能被cv限定。(注意:函数调用或转换表达式可能会导致非类cv限定类型的prvalue,但会立即删除cv限定符。)

The returned const int is just a normal int prvalue, and makes the non-const overload a better match than the const one.

返回的const int只是一个普通的int prvalue,并且使得非const重载比const更匹配。

#2


22  

Why do primitive and user-defined types act differently when returned as 'const' from a function?

为什么原语类型和用户定义类型在从函数返回“const”时行为不同?

Because const part is removed from primitive types returned from functions. Here's why:

因为const部分从函数返回的原始类型中删除。原因如下:

In C++11 from § 5 Expressions [expr] (p. 84):

在c++中11从§5表达式[expr](p。84):

8

8

Whenever a glvalue expression appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue (4.1), array-to-pointer (4.2), or function-to-pointer (4.3) standard conversions are applied to convert the expression to a prvalue. [Note: because cv-qualifiers are removed from the type of an expression of non-class type when the expression is converted to a prvalue, an lvalue expression of type const int can, for example, be used where a prvalue expression of type int is required. —end note]

每当glvalue表达式作为运算符的操作数出现时,就会应用lvalue-to-rvalue(4.1)、arrayto -pointer(4.2)或functionto -pointer(4.3)标准转换将表达式转换为prvalue。[注意:由于在将表达式转换为prvalue时,从非类类型的表达式类型中删除了cv-限定符,例如,如果需要类型int的prvalue表达式,则可以使用类型const int的lvalue表达式。端注)

And similarly from § 5.2.3 Explicit type conversion (functional notation) [expr.type.conv] (p. 95):

同样从§5.2.3显式类型转换(功能符号)[expr.type。conv](p。95):

2

2

The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates a prvalue of the specified type,which is valueinitialized (8.5; no initialization is done for the void() case). [Note: if T is a non-class type that is cv-qualified, the cv-qualifiers are ignored when determining the type of the resulting prvalue (3.10). —end note]

表达式T(),其中T是一个简单类型说明符或用于非数组完整对象类型或(可能是cv限定的)void类型的typenam -说明符,创建指定类型的prvalue,它是valueinitialized (8.5;对void()情况不进行初始化。[注意:如果T是cv限定的非类类型,则在确定结果prvalue的类型时忽略cv限定符(3.10)。端注)

What that means is that const int prvalue returned by g2() is effectively treated as int.

这意味着g2()返回的const int prvalue被有效地视为int。

#3


13  

Quotes from the standard,

引用的标准,

§8/6 Expressions [expr]

§8/6表达式(expr)

If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

如果prvalue最初具有“cv T”类型,其中T是cv不合格的非类、非数组类型,在进行进一步分析之前,表达式的类型将调整为T。

and §8/9 Expressions [expr]

和§8/9表达式(expr)

(emphasis mine)

(强调我的)

Whenever a glvalue expression appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue, array-to-pointer, or function-to-pointer standard conversions are applied to convert the expression to a prvalue. [ Note: Because cv-qualifiers are removed from the type of an expression of non-class type when the expression is converted to a prvalue, an lvalue expression of type const int can, for example, be used where a prvalue expression of type int is required. — end note ]

每当glvalue表达式作为运算符的操作数出现时,就应用lvalue-to-rvalue、arrayto -pointer或function-to-pointer标准转换将表达式转换为prvalue。[注意:由于在将表达式转换为prvalue时,从非类类型的表达式类型中删除了cv-限定符,例如,如果需要类型int的prvalue表达式,则可以使用类型const int的lvalue表达式。——结束注意)

So for g2(), int is a non-class type, and (the return value of) g2() is a prvalue expression, then const qualifier is removed, so the return type is not const int, but int. That's why f(T&&) is called.

因此,对于g2(), int是一个非类类型,并且()g2()的返回值是一个prvalue表达式,那么const限定符被删除,因此返回类型不是const int,而是int.这就是为什么调用f(T&&)。

#4


2  

The previous answers are perfectly valid. I just want to add a potential motivation why it may sometimes be useful to return const objects. In the following example, class A gives a view on internal data from class C, which in some cases shall not be modifyable (Disclaimer, for brevity some essential parts are left out -- also there are likely easier ways to implement this behavior):

前面的答案是完全正确的。我只是想添加一个潜在的动机,为什么返回const对象有时是有用的。在下面的例子中,类A给出了关于类C的内部数据的视图,在某些情况下这些数据是不可修改的(声明,为了简洁性,省略了一些重要的部分——也有可能有更简单的方法来实现这种行为):

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}