web-dev-qa-db-ja.com

関数から '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.
}

問題の説明はコードに埋め込まれています。私のコンパイラはclang 5.0

私はただ疑問に思います:

このような場合、C++は組み込み型とカスタム型を異なる方法で処理するのはなぜですか?

42
xmllmx

標準からの引用はありませんが、 cppreference は私の疑いを確認します:

非クラス非配列prvalueはcv修飾できません。 (注:関数呼び出しまたはキャスト式は、クラスではないcv修飾型のprvalueになる可能性がありますが、cv修飾子はすぐに取り除かれます。)

返されたconst intは通常のint prvalueであり、非constオーバーロードをconstのものよりも適切に一致させます。

29

関数から 'const'として返されたときに、プリミティブ型とユーザー定義型の動作が異なるのはなぜですか?

関数から返されたプリミティブ型からconst部分が削除されているためです。理由は次のとおりです。

C++ 11 から_§ 5 Expressions [expr]_(p。

8

Glvalue式が、そのオペランドのprvalueを期待する演算子のオペランドとして現れる場合は常に、左辺値から右辺値(4.1)、配列からポインタ(4.2)、または関数からポインタ(4.3)の標準変換は式をprvalueに変換するために適用されます。 [注:式がprvalueに変換されると、非クラス型の式の型からcv修飾子が削除されるため、たとえば、型const intのlvalue式は、型intのprvalue式で使用できます。必要とされている。 —エンドノート]

同様に、§ 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]から(p。

2

式T()、ここでTは、配列以外の完全なオブジェクト型または(場合によってはcv修飾された)void型の単純型指定子または型名指定子で、指定された型のprvalueを作成します。 8.5; void()の場合、初期化は行われません。 [注:Tがcv修飾された非クラス型である場合、結果のprvalueの型を決定するときにcv修飾子は無視されます(3.10)。 —エンドノート]

つまり、g2()によって返される_const int_ prvalue は、実質的にintとして扱われます。

22
Pavel P

標準からの引用、

§8/ 6式[式]

Prvalueのタイプが「cv T」である場合(Tはcv非修飾の非クラス、非配列タイプ)、式のタイプは、さらに分析する前にTに調整されます。

および §8/ 9式[expr]

(強調鉱山)

Glvalue式が、そのオペランドのprvalueを期待する演算子のオペランドとして現れる場合は常に、lvalueからrvalue、配列からポインター、または関数からポインターへの標準変換が適用され、式がprvalueに変換されます。 [注:cv修飾子は、式がprvalueに変換されるときに非クラス型の式の型から削除されるため、型_const int_の左辺値式は、たとえば、タイプintのprvalue式が必要な場合に使用されます。 —メモ終了]

したがって、g2()の場合、intは非クラス型であり、(の戻り値)g2()prvalue expression であり、 const修飾子が削除されたため、戻り値の型は_const int_ではなくintになります。そのため、f(T&&)が呼び出されます。

13
songyuanyao

以前の答えは完全に有効です。 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
}
2
chtz