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

履歴 編集

class template
<type_traits>

std::is_trivially_copyable(C++11)

namespace std {
  template <class T>
  struct is_trivially_copyable;

  template <class T>
  inline constexpr bool is_trivially_copyable_v
    = is_trivially_copyable<T>::value;          // C++17
}

概要

Tがトリビアルコピー可能か調べる。

要件

remove_all_extents<T>::typeは、完全型か、const/volatile修飾された(あるいはされていない)voidでなければならない。

効果

is_trivially_copyableは、Tがトリビアルコピー可能な型であるならばtrue_typeから派生し、そうでなければfalse_typeから派生する。

「トリビアルコピー可能な型」とは、「std::memcpy()可能な型である」と言い換えることもできる。

トリビアルコピー可能な型の定義はこれまで2回変更されている。CWG issue 1734による変更と、C++20での変更の2つだ。
CWG issue 1734は2013年8月9日に報告されている。つまりC++14に対応した処理系はこれに対応している可能性があり、すくなくともC++17時点で規格書に修正が取り込まれている。

前提となる用語

user-providedな関数とは、= default/= delete指定されていない、ユーザーによって宣言された関数である。

C++20

資格のある特殊メンバ関数

資格のある特殊メンバ関数とは、特殊メンバ関数のうち、次の条件を満たすものを言う。

  • = delete指定されていない
  • 制約されている場合、それを満たしている
  • 同じ種類の特殊メンバ関数の中でもっとも強く制約されている

同じ種類の特殊メンバ関数とは簡単に言うと、制約を除いて同一の宣言となる特殊メンバ関数のことである。

制約の詳細についてはコンセプトにある解説を参照のこと。

例えば下の例を見てほしい。optionalクラスは2つのコピーコンストラクタを持っている(#1と#2)。
これらは最初の引数の型が等しいので、2つの同じ種類のコピーコンストラクタを持っているといえる。
これらはいずれも= delete指定されていない。

T=std::unique_ptr<int>の場合、#1と#2はどちらも制約を満たさないので資格のあるコピーコンストラクタを持たない。
T=std::stringの場合、#2のみ制約を満たすので#2だけが資格のあるコピーコンストラクタである。
T=intの場合、#1と#2はどちらも制約を満たすが、#1のほうが#2より強く制約されているので、#1だけが資格のあるコピーコンストラクタである。

#include <type_traits>
template <typename T>
concept trivially_copy_constructible = std::is_trivially_copy_constructible_v<T>;
template <typename T>
struct optional {
    // #1
    optional(optional const&)
        requires trivially_copy_constructible<T> && std::copy_constructible<T>
        = default;
    // #2
    optional(optional const& rhs)
            requires std::copy_constructible<T>;
};

トリビアルコピー可能な型

トリビアルコピー可能な型とは、次の条件を満たすものをいう。

  1. 少なくとも1つの資格のあるコピー/ムーブ コンストラクタ/代入演算子がある
  2. 資格のあるコピー/ムーブ コンストラクタ/代入演算子それぞれはtrivialである
  3. trivialで= delete指定されていないデストラクタを持つ

これをもうすこし具体的に解釈する。

まずそのクラス自体に対して次の条件を満たす必要がある。

  • デストラクタはvirtualではない
  • デストラクタは= delete指定されていない
  • デストラクタと全ての資格のあるコピー/ムーブ コンストラクタ/代入演算子はuser-providedではない
  • virtual関数を持たず、virtual基本クラスも持たない
  • 少なくとも1つの資格のあるコピー/ムーブ コンストラクタ/代入演算子がある

対象となるクラスの非静的メンバ変数及び全ての基底クラスもトリビアルコピー可能でなければならない

C++17もしくはCWG issue 1734が適用された処理系

トリビアルコピー可能な型とは、次の条件を満たすものをいう。

  • 全てのコピー/ムーブ コンストラクタ/代入演算子はtrivialもしくは= delete指定されている
  • コピー/ムーブ コンストラクタ/代入演算子のうち少なくとも1つは= delete指定されていない
  • trivialで= delete指定されていないデストラクタを持つ

これをもうすこし具体的に解釈する。

まずそのクラス自体に対して次の条件を満たす必要がある。

  • デストラクタはvirtualではない
  • デストラクタは= delete指定されていない
  • デストラクタと全てのコピー/ムーブ コンストラクタ/代入演算子はuser-providedではない
  • virtual関数を持たず、virtual基本クラスも持たない
  • コピー/ムーブ コンストラクタ/代入演算子のうち少なくとも1つは= delete指定されていない

対象となるクラスの非静的メンバ変数及び全ての基底クラスもトリビアルコピー可能でなければならない

CWG issue 1734が適用されていないC++14以前の処理系

トリビアルコピー可能な型とは、次の条件を満たすものをいう。

  • 非トリビアルなコピーコンストラクタを持っていないこと
  • 非トリビアルなムーブコンストラクタを持っていないこと
  • 非トリビアルなコピー代入演算子を持っていないこと
  • 非トリビアルなムーブ代入演算子を持っていないこと
  • トリビアルなデストラクタを持っていること

これをもうすこし具体的に解釈する。

まずそのクラス自体に対して次の条件を満たす必要がある。

  • デストラクタはvirtualではない
  • デストラクタと全てのコピー/ムーブ コンストラクタ/代入演算子はuser-providedではない
  • virtual関数を持たず、virtual基本クラスも持たない

対象となるクラスの非静的メンバ変数及び全ての基底クラスもトリビアルコピー可能でなければならない

しかし、デストラクタが削除されていてもトリビアルコピー可能となる問題があった。

#include <type_traits>
#include <concepts>
#include <memory>
#include <string>
struct C1 {
  // 非トリビアルな特殊関数を持っていない

  // 特殊関数ではないメンバ関数は持っている
  int f(int x) const { return x; }
};

struct C2 {
  // デストラクタはuser-provided
  ~C2() {}
};

struct TrivialDestructor {
  // デストラクタはdefault指定されており、user-providedではない
  ~TrivialDestructor() = default;
};

struct DeletedDestructor {
  // デストラクタはdeleteされている
  ~DeletedDestructor() = delete;
};
#ifdef __cpp_concepts
template <typename T>
concept trivially_copy_constructible = std::is_trivially_copy_constructible_v<T>;
template <typename T>
struct optional {
  alignas(T) std::byte value[sizeof(T)];
  bool engaged;
  // #1: default指定されており、user-providedではない
  optional(optional const&)
      requires trivially_copy_constructible<T> && std::copy_constructible<T>
      = default;

  // #2: user-providedなコピーコンストラクタ
  optional(optional const& rhs)
          requires std::copy_constructible<T>
      : engaged(rhs.engaged)
  {
      if (engaged) {
          new (value) T(rhs.value);
      }
  }
};
#endif
// 組み込み型は全てトリビアルコピー可能
static_assert(std::is_trivially_copyable<int>::value == true, "int is trivially copyable");
static_assert(std::is_trivially_copyable<int*>::value == true, "int* is trivially copyable");

// ユーザー定義型
static_assert(std::is_trivially_copyable<C1>::value == true, "C1 is trivially copyable");
static_assert(std::is_trivially_copyable<C2>::value == false, "C2 isn't trivially copyable");
static_assert(std::is_trivially_copyable<TrivialDestructor>::value == true, "TrivialDestructor is trivially copyable");

// CWG issue 1734が適用された環境ではトリビアルコピー可能ではない
static_assert(std::is_trivially_copyable<DeletedDestructor>::value == false, "DeletedDestructor isn't trivially copyable");

#ifdef __cpp_concepts
// #1も#2も資格のあるコピーコンストラクタではなく、他に資格のあるコピー/ムーブ コンストラクタ/代入演算子がないためトリビアルコピー可能ではない
static_assert(std::is_trivially_copyable<optional<std::unique_ptr<int>>>::value == false, "std::unique_ptr<int> isn't trivially copyable");
// #2が資格のあるコピーコンストラクタであるが、user-providedであるためトリビアルコピー可能ではない
static_assert(std::is_trivially_copyable<optional<std::string>>::value == false, "optional<std::string> isn't trivially copyable");
// #1が資格のあるコピーコンストラクタであるのでトリビアルコピー可能
static_assert(std::is_trivially_copyable<optional<int>>::value == true, "optional<int> is trivially copyable");
#endif
int main() {}

出力

バージョン

言語

  • C++11

処理系

  • Clang: 3.0
  • GCC: 5.0
  • Visual C++: 2012, 2013, 2015
    • 2012~2013は、正しく実装されていない。is_trivially_copy_constructible<T, U>と同一の実装になっている。

参照