簡體   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.
}

問題描述嵌入在代碼中。 我的編譯器是clang 5.0

我只是好奇:

在這種情況下,為什么C ++會以不同方式處理內置類型和自定義類型?

我沒有標准的引用,但是cppreference證實了我的懷疑:

非類非數組prvalue不能是cv限定的。 (注意:函數調用或強制轉換表達式可能會導致非類cv限定類型的prvalue,但會立即刪除cv-qualifier。)

返回的const int只是一個普通的int prvalue,它使得非const重載比const更好。

當從函數返回為“const”時,為什么原始類型和用戶定義類型的行為不同?

因為const部分從函數返回的基本類型中刪除。 原因如下:

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

8

每當glvalue表達式作為操作符的操作數出現時,該操作符需要該操作數的prvalue,左值到右值(4.1),數組到指針(4.2)或函數到指針(4.3)標准轉換是用於將表達式轉換為prvalue。 [注意:因為當表達式轉換為prvalue時,cv-quali firs從非類型表達式的類型中刪除,例如,類型為const int的左值表達式可以在類型為int的prvalue表達式中使用是必須的。 - 尾注]

類似於§ 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]§ 5.2.3 Explicit type conversion (functional notation) [expr.type.conv] ):

2

表達式T(),其中T是非數組完整對象類型或(可能是cv-quali fi ed)void類型的簡單類型指定者或typename-speci fi er,它創建了一個特定類型的prvalue,它是有價值的( 8.5;沒有對void()情況進行初始化)。 [注意:如果T是具有cv-quali fi ed的非類型類型,則在確定結果prvalue(3.10)的類型時將忽略cv-quali firs。 - 尾注]

這意味着g2()返回的const int prvalue被有效地視為int

標准引用,

§8/ 6表達式[expr]

如果prvalue最初具有類型“cv T”,其中T是cv非限定的非類非數組類型,則在進行任何進一步分析之前將表達式的類型調整為T.

§8/ 9表達式[expr]

(強調我的)

每當glvalue表達式作為操作符的操作數出現,該操作符需要該操作數的prvalue時,將應用左值到右值,數組到指針或函數到指針的標准轉換來將表達式轉換為prvalue。 [注意:因為當表達式轉換為prvalue時,cv-qualifiers從非類型表達式的類型中刪除,所以例如,可以在類型為int的prvalue表達式的情況下使用類型為const int的左值表達式是必須的。 - 結束說明]

因此對於g2()int是非類型類型,並且(返回值) g2()prvalue表達式 ,然后刪除const限定符,因此返回類型不是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
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM