简体   繁体   English

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

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

#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 ++会以不同方式处理内置类型和自定义类型?

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. 非类非数组prvalue不能是cv限定的。 (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.) (注意:函数调用或强制转换表达式可能会导致非类cv限定类型的prvalue,但会立即删除cv-qualifier。)

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更好。

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. 因为const部分从函数返回的基本类型中删除。 Here's why: 原因如下:

In C++11 from § 5 Expressions [expr] (p. 84): § 5 Expressions [expr] C ++ 11中 (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. 每当glvalue表达式作为操作符的操作数出现时,该操作符需要该操作数的prvalue,左值到右值(4.1),数组到指针(4.2)或函数到指针(4.3)标准转换是用于将表达式转换为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. [注意:因为当表达式转换为prvalue时,cv-quali firs从非类型表达式的类型中删除,例如,类型为const int的左值表达式可以在类型为int的prvalue表达式中使用是必须的。 —end note] - 尾注]

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

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). 表达式T(),其中T是非数组完整对象类型或(可能是cv-quali fi ed)void类型的简单类型指定者或typename-speci fi er,它创建了一个特定类型的prvalue,它是有价值的( 8.5;没有对void()情况进行初始化)。 [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). [注意:如果T是具有cv-quali fi ed的非类型类型,则在确定结果prvalue(3.10)的类型时将忽略cv-quali firs。 —end note] - 尾注]

What that means is that const int prvalue returned by g2() is effectively treated as int . 这意味着g2()返回的const int prvalue被有效地视为int

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. 每当glvalue表达式作为操作符的操作数出现,该操作符需要该操作数的prvalue时,将应用左值到右值,数组到指针或函数到指针的标准转换来将表达式转换为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. [注意:因为当表达式转换为prvalue时,cv-qualifiers从非类型表达式的类型中删除,所以例如,可以在类型为int的prvalue表达式的情况下使用类型为const int的左值表达式是必须的。 — end note ] - 结束说明]

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 . 因此对于g2()int是非类型类型,并且(返回值) g2()prvalue表达式 ,然后删除const限定符,因此返回类型不是const int ,而是int That's why f(T&&) is called. 这就是调用f(T&&)的原因。

The previous answers are perfectly valid. 以前的答案是完全有效的。 I just want to add a potential motivation why it may sometimes be useful to return const objects. 我只想添加一个潜在的动机,为什么它有时可能对返回const对象有用。 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): 在下面的示例中, class A给出了来自class 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
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何定义运算符,以便可以将用户定义类型的数组转换为原始类型的数组? - How can I define operators so that a array of user-defined types can be transformed into an array of primitive types? 原始 VS 用户定义数据类型的 new 和 delete 的时间复杂度是多少? - What is the time complexity of new and delete for primitive VS user-defined data types? 为什么在传递给函数时C ++中的多维数组的行为不同? - Why do multidimensional arrays in C++ act differently when passed to a function? 基于用户定义的概念从命名空间 std 中专门化类型 - Specializing types from namespace std based on user-defined concepts C++中从用户定义类型到基本类型的隐式转换 - Implicit conversion from user-defined type to primitive type in C++ 在初始化 class 类型时,C++ 可以执行多少次隐式转换才能将一种用户定义类型转换为另一种? - How many implicit conversions can C++ do to convert one user-defined type to another when initializing class types? 为什么我们只对用户定义的数据类型使用箭头和点运算符,为什么不对原始数据类型使用? - why we using arrow and dot opertor only for user defined data types why not for primitive data types? 用户定义的标准类型推导指南 - User-defined deduction guide for std types 了解不相关类型的用户定义转换 - Understanding User-Defined Conversions for Unrelated Types 用户定义类型C ++的添加 - Addition on user-defined types C++
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM