为什么基元类型和用户定义类型在从函数返回为"const"时的行为不同?

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

本文关键字:类型 const 函数 用户 定义 为什么 返回      更新时间:2023-10-16
#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.
}

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

我只是想知道:

为什么C 在这种情况下对内置类型和自定义类型的处理方式不同?

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

不能对非阶级非阵列prvalue进行CV测试。(注意:函数调用或铸造表达式可能会导致非类CV合格类型的序言,但CV-Qualifier立即删除。(

返回的const int只是一个普通的int prvalue,使得非const超载比const One更好。

当函数返回为" const"时,为什么原始和用户定义的类型的作用有所不同?

因为const零件是从功能返回的原始类型中删除的。原因是:

§ 5 Expressions [expr]中的C 11中(第84页(:

8

每当glvalue表达式以操作员的操作数出现时 期望该操作数,lvalue-to-rvalue(4.1(, 阵列到点(4.2(或功能对二重奏(4.3(标准转换为 应用于将表达式转换为prvalue。[注意:因为CV-Quali-firer 当 表达被转换为prvalue,一种类型的LVALUE表达 例如,可以使用const -int在类型的prvalue表达式的情况下使用 int是必需的。 - 末尾注]

类似地来自§ 5.2.3 Explicit type conversion (functional notation) [expr.type.conv](第95页(:

2

表达式t((,其中t是简单型特异性或 非阵列完整对象类型或 (可能是cv-qualifif(void类型,创建了指定的 类型,即有价值的(8.5;对于 void((案例(。[注意:如果t是一种非课堂类型,则是cv qualifif的 确定结果的类型时,忽略了CV-Qualifirer Prvalue(3.10(。 - 末尾注]

这意味着const intg2()返回的prvalue有效地将其视为int

引用标准,

§8/6表达式[expr]

如果prvalue最初具有" cv t"类型,其中t是一个 CV UNCALIFIFIED非级别的非阵列类型,表达式的类型 在进行任何进一步分析之前已将其调整为t。

和§8/9表达式[expr]

(强调我的(

每当glvalue表达式以操作符的操作数出现时 这预计该操作数,lvalue-to-rvalue, 阵列到二次或函数对置标准转换是 应用于将表达式转换为prvalue。 [注意:因为 从非类别的表达式中删除了CV-质量 当将表达式转换为prvalue时键入lvalue 例如,const int类型的表达可以使用prvalue的地方使用 需要int类型的表达。 - 终点注]

so对于g2()int是一种非类型类型,并且(返回值(g2()是prvalue表达式,然后删除const预选赛,因此返回类型不是const int,而是int。这就是为什么f(T&&)被称为。

以前的答案是完全有效的。我只想添加潜在的动机,为什么有时返回const对象可能有用。在下面的示例中,class Aclass 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
}

相关文章: