为啥原始类型和用户定义类型在从函数返回为“const”时表现不同?
Posted
技术标签:
【中文标题】为啥原始类型和用户定义类型在从函数返回为“const”时表现不同?【英文标题】:Why do primitive and user-defined types act differently when returned as 'const' from a function?为什么原始类型和用户定义类型在从函数返回为“const”时表现不同? 【发布时间】:2017-10-03 12:54:39 【问题描述】:#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++ 会以不同的方式对待内置类型和自定义类型?
【问题讨论】:
clang编译警告warning: 'const' type qualifier on return type has no effect [-Wignored-qualifiers]
如果构建代码并启用所有警告(-Wall -Wextra
on gcc),这些问题本可以避免。
@JonasWielicki:出于向后兼容性的原因,这不会启用所有警告,这就是 Clang 引入 -Weverything
的原因:)
@JonasWielicki:基本上,GCC 没有等价物。粗略地说 GCC -Wall
打开所有在引入时存在的警告,但没有任何新警告,因为担心这会阻止使用 -Wall -Werror
的第三方软件的人升级 GCC(这是一个非常有效的担忧,我不应该,看到 GCC 用于构建许多 Linux 发行版)。后来他们添加了-Wextra
来覆盖丢失的警告集,并且出于同样的原因它被稳定了,因此不包括之后包含的警告......
@MatthieuM。 GCC确实不时添加到-Wall
和-Wextra
; -Wall
的实际政策类似于“几乎可以肯定表明存在错误,即使没有,也可以轻松更改代码以消除警告”,-Wextra
的实际政策类似,但估计的指示几率较低一个错误。您可能已经注意到,clang 的 -Weverything
包含警告,如果代码按原样正确,则没有解决方法 - 这是 GCC 人们想要避免的。
【参考方案1】:
我没有引用标准,但cppreference 证实了我的怀疑:
非类非数组纯右值不能是 cv 限定的。 (注意:函数调用或强制转换表达式可能会导致非类 cv 限定类型的纯右值,但 cv 限定符会立即被去除。)
返回的const int
只是一个普通的int
prvalue,并且使非常量重载比const
更匹配。
【讨论】:
@alfC 因为const A
表示A
的所有成员都不能更改(即使您复制A),而对于int 甚至没有成员存在。
@alfC Looks like 是为了阻止人们用临时类对象做奇怪的事情。
这就留下了为什么 C++ 允许类纯右值是 cv 限定的问题。恕我直言,这与允许它用于非类纯右值一样没有意义。
@cmaster 这有点道理,即它不允许您在类prvalue上调用非常量成员函数。我现在没有示例,但我想可以想出一些场景,例如***.com/q/8716330/3093378。当然在 C++11 中不应该这样做,因为它会使移动语义停止工作。
@vsoftco 抱歉,我仍然看不到允许对类对象进行 cv 限定的意义:如果我正确阅读,prvalue 是具有值语义(!)的操作的瞬态结果,其中prvalue归调用操作的代码所有。恕我直言,限制是否允许调用者修改这样的值不是操作本身的业务。毕竟,调用它的析构函数最终销毁值是调用者的事情。根据定义,这是一个非常量操作,必须允许它避免创建不可删除的临时对象。【参考方案2】:
为什么原始类型和用户定义类型在从函数返回为“const”时表现不同?
因为const
部分已从函数返回的原始类型中删除。原因如下:
在 C++11 中来自 § 5 Expressions [expr]
(第 84 页):
8
每当一个泛左值表达式作为一个运算符的操作数出现时 期望该操作数的右值,左值到右值(4.1), 数组到指针 (4.2) 或函数到指针 (4.3) 的标准转换是 应用于将表达式转换为纯右值。 [注意:因为 cv-qualifiers 从非类类型表达式的类型中删除 表达式被转换为纯右值,类型为左值表达式 例如,const int 可以用于类型为纯右值表达式的地方 int 是必需的。 ——尾注]
同样来自§ 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]
(第 95 页):
2
表达式 T(),其中 T 是简单类型说明符或 typename-specifier 用于非数组完整对象类型或 (可能是 cv 限定的)void 类型,创建指定的纯右值 类型,它是 valueinitialized (8.5; 没有为 void() 情况)。 [注意:如果 T 是 cv 限定的非类类型,则 在确定结果的类型时忽略 cv 限定符 prvalue (3.10)。 ——尾注]
这意味着g2()
返回的const int
prvalue 被有效地视为int
。
【讨论】:
【参考方案3】:引用标准,
§8/6 Expressions [expr]
如果纯右值最初的类型是“cv T”,其中 T 是 cv-unqualified 非类,非数组类型,表达式的类型 在任何进一步分析之前调整为 T。
和§8/9 Expressions [expr]
(强调我的)
当一个泛左值表达式作为一个运算符的操作数出现时 期望该操作数的纯右值,左值到右值, 数组到指针或函数到指针的标准转换是 应用于将表达式转换为纯右值。 [ 注意:因为 从非类表达式的类型中删除 cv 限定符 表达式转换为纯右值时的类型,左值 例如,
const int
类型的表达式可以用于纯右值int
类型的表达式是必需的。 — 尾注 ]
所以对于g2()
,int
是一个非类类型,而(的返回值)g2()
是一个prvalue expression,那么const
限定符被去掉,所以返回类型不是@ 987654331@,但int
。这就是调用f(T&&)
的原因。
【讨论】:
规范措辞在[expr]/6。【参考方案4】:前面的答案是完全有效的。我只是想添加一个潜在的动机,为什么有时返回 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
【讨论】:
以上是关于为啥原始类型和用户定义类型在从函数返回为“const”时表现不同?的主要内容,如果未能解决你的问题,请参考以下文章