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

#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. } 

问题描述embedded在代码中。 我的编译器是clang 5.0

我只是好奇:

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

我没有这个标准的引用,但是引用证实了我的怀疑:

非类非数组prvalue不能被cv限定。 (注意:函数调用或转换expression式可能会导致非class cv-qualifiedtypes的值,但cv-qualifier会立即被除去。)

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

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

因为const部分从函数返回的原始types中移除。 原因如下:

§ 5 Expressions [expr] (p。84)的C ++ 11中 :

8

每当一个glvalueexpression式作为操作数的操作数出现时,左值到右值(4.1),数组到指针(4.2)或函数到指针(4.3)的标准转换是用于将expression式转换为前值。 [注意:当expression式转换为prvalue时,因为cv-限定符从非typesexpression式的types中被移除,例如,types为const int的左值expression式可以用于inttypes的prvalueexpression式是必须的。 – 注意]

§ 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]§ 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]

2

expression式T(),其中T是非数组完整对象types的简单types指定器或types名指定器,或者(可能是cv合格的)voidtypes,创build一个指定types的prvalue,它是value初始化的8.5;没有初始化为void()情况)。 [注意:如果T是一个cv合格的非typestypes,那么在确定结果值(3.10)的types时,cv合格者将被忽略。 – 注意]

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

从标准行情来看,

§8/ 6expression式[expr]

如果一个prvalue最初具有“cv T”types,其中T是一个cv不合格的非类非数组types,那么在进一步分析之前,expression式的types将被调整为T.

和§8/ 9expression式[expr]

(重点是我的)

每当一个glvalueexpression式作为一个操作数的操作数出现,该操作数需要该操作数的一个prvalue时,应用左值到右值,数组到指针或函数到指针的标准转换来将expression式转换为一个prvalue。 [注意:因为当expression式转换为prvalue时,cv-qualifiers从非typesexpression式的types中被移除,例如,types为const int的左值expression式可以用于inttypes的prvalueexpression式是必须的。 – 结束注意]

所以对于g2()int是一个非类types,并且( g2()的返回值是一个prvalueexpression式 ,那么const限定符就被移除了,所以返回types不是const int ,而是int 。 这就是为什么f(T&&)被调用。

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