为什么原始类型和用户定义类型在从函数返回为 '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.
}
问题描述嵌入在代码中。我的编译器是 clang 5.0
.
我只是想知道:
为什么 C++ 在这种情况下会区别对待内置类型和自定义类型?
我没有引用标准,但 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.)
返回的 const int
只是一个普通的 int
纯右值,并且使非常量重载比 const
更匹配。
Why do primitive and user-defined types act differently when returned as 'const' from a function?
因为 const
部分从函数返回的原始类型中删除。原因如下:
在 C++11 来自 § 5 Expressions [expr]
(第 84 页):
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]
与 § 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]
(第 95 页)类似:
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]
这意味着 g2()
返回的 const int
prvalue 被有效地视为 int
.
引用标准,
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.
(强调我的)
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 ]
因此对于 g2()
,int
是非 class 类型,并且 g2()
的(return 值)是 prvalue expression,那么 const
限定符被移除,因此 return 类型不是 const int
,而是 int
。这就是 f(T&&)
被调用的原因。
前面的回答完全正确。我只是想添加一个潜在的动机,为什么它有时可能对 return 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
}
#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 证实了我的怀疑:
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.)
返回的 const int
只是一个普通的 int
纯右值,并且使非常量重载比 const
更匹配。
Why do primitive and user-defined types act differently when returned as 'const' from a function?
因为 const
部分从函数返回的原始类型中删除。原因如下:
在 C++11 来自 § 5 Expressions [expr]
(第 84 页):
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]
与 § 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]
(第 95 页)类似:
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]
这意味着 g2()
返回的 const int
prvalue 被有效地视为 int
.
引用标准,
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.
(强调我的)
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 typeint
is required. — end note ]
因此对于 g2()
,int
是非 class 类型,并且 g2()
的(return 值)是 prvalue expression,那么 const
限定符被移除,因此 return 类型不是 const int
,而是 int
。这就是 f(T&&)
被调用的原因。
前面的回答完全正确。我只是想添加一个潜在的动机,为什么它有时可能对 return 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
}