最終更新日時(UTC):
が更新

履歴 編集

class template
<type_traits>

std::reference_converts_from_temporary(C++23)

namespace std {
  template<class T, class U>
  struct reference_converts_from_temporary;
}

概要

参照Tが一時オブジェクトUをコピー初期化(代入形式による初期化)で束縛した時、その一時オブジェクトの寿命を延長するかを判定する。

INVOKE<R>など、暗黙的な型変換のみが考慮される文脈におけるダングリング参照の生成を回避するために使用される。

なお、直接初期化(丸カッコによる初期化)における文脈ではreference_constructs_from_temporaryが使用される。

要件

TUのどちらも、完全型const/volatile修飾された(あるいはされていない)void、要素数不明の配列型であること。

効果

VAL<U>を次のように定義する。

  • Uが参照型や関数型の場合、declval<U>()と同じ型と値カテゴリを持つ式
  • Uが参照型や関数型でない場合、型Uであるprvalue(ただし、Uconst/volatile修飾があれば調整される)

conjunction_v<is_reference<T>, is_convertible<U, T>>trueかつ、T t(VAL<U>)においてtが一時オブジェクトの寿命を延長する場合にtrue_typeから派生し、そうでなければfalse_typeから派生する。

備考

多くのメタ関数はprvaluexvalueを区別しないが、このメタ関数は区別する。例えば、右辺値参照に「戻り値の型が右辺値参照である関数」の戻り値を束縛することを考える。(再現コードは、説明の最後に付す)

この事自体は即座に不適格とはならない。しかし、その関数が実際にはprvalueを返しているとすれば、そのprvalueは戻り値の時点で右辺値参照に束縛されるため、それを右辺値参照に束縛したとしても寿命は延長されることはない。そのため、最終的にダングリング参照を生じることとなる。

このような場面においては、本メタ関数がprvaluexvalueを、prvalueT(参照なしの型)として、xvalueT&&(右辺値参照である型、転送参照(Forwarding Reference) を意味しているわけではない)として区別すれば検出が可能となる。

#include <iostream>

struct S {
  S() { std::cout << "S construct" << std::endl; }
  ~S() { std::cout << "S destruct" << std::endl; }
};

S&& f() { return S{}; }

int main() {
  std::cout << "1" << std::endl;

  // f の戻り値は s に束縛されて寿命が延長されたか?
  // 実際にはこの宣言の1文が終了した際に f の戻り値は破棄されている
  S&& s = f();

  std::cout << "2" << std::endl;
}

出力

1
S construct
S destruct
2

#include <type_traits>

struct A {
    A() = default;
    A(int) {}
};

struct B : A {
    explicit B(int) {}
};

struct C {
    operator struct A() { return A{}; }
    explicit operator struct B() { return B{0}; }
};

int main()
{
    // OK: わかりやすく寿命が延長されるタイプ
    static_assert(std::reference_converts_from_temporary_v<int&&, int>);
    static_assert(std::reference_converts_from_temporary_v<const int&, int>);
    static_assert(std::reference_converts_from_temporary_v<A&&, B>);
    static_assert(std::reference_converts_from_temporary_v<const A&&, B>);

    // OK: 変換されて rvalue になってから束縛されて寿命が延長されるタイプ
    static_assert(std::reference_converts_from_temporary_v<int&&, long>);
    static_assert(std::reference_converts_from_temporary_v<int&&, long&>);
    static_assert(std::reference_converts_from_temporary_v<int&&, long&&>);

    // OK: explicit ではないので変換されて rvalue になってから束縛されて寿命が延長されるタイプ
    static_assert(std::reference_converts_from_temporary_v<A&&, C>);
    static_assert(std::reference_converts_from_temporary_v<A&&, C&>);
    static_assert(std::reference_converts_from_temporary_v<A&&, C&&>);
    static_assert(std::reference_converts_from_temporary_v<A&&, int>);


    // NG: const ではない左辺値参照は寿命を延長しないんですタイプ
    //     1つ目3つ目はそもそも参照そのものが構築出来ない
    static_assert(false == std::reference_converts_from_temporary_v<int&, int>);
    static_assert(false == std::reference_converts_from_temporary_v<int&, int&>);
    static_assert(false == std::reference_converts_from_temporary_v<int&, int&&>);

    // NG: 構築できないパターンと右辺値参照は区別するパターン
    static_assert(false == std::reference_converts_from_temporary_v<int&&, int&>);
    static_assert(false == std::reference_converts_from_temporary_v<int&&, int&&>);

    // NG: explicit なので変換出来ずに詰むパターン
    static_assert(false == std::reference_converts_from_temporary_v<B&&, C>);
    static_assert(false == std::reference_converts_from_temporary_v<B&&, C&>);
    static_assert(false == std::reference_converts_from_temporary_v<B&&, C&&>);
    static_assert(false == std::reference_converts_from_temporary_v<B&&, int>);
}

出力

バージョン

言語

  • C++23

処理系

関連項目

参照