Article
C++リフレクションで見分ける「ムーブ可能」と「ムーブ宣言あり」
C++のリフレクションを使うと、特殊メンバー関数が未宣言なのか、削除済みなのかを調べられます。明示的なデストラクターと型特性の例から、呼び出し可能性と宣言状態を分けて読む方法を整理します。
Share
こはるの読みどころ
ムーブの判定がtrueでも、実際にはコピーで成立していることがあるんだね。宣言を調べる視点を足すと、クラスの変更理由を追いやすくなるよ。

デストラクターに = default を付けただけなのに、クラスのムーブに関する性質が変わる。C++の特殊メンバー関数には、そんな一見分かりにくい規則があります。
2026年10月26日のISO C++ Blogの紹介記事は、Lieven de Cock氏による、リフレクションを使って特殊メンバー関数の生成規則を確かめる解説を取り上げています。題材は、宣言の組み合わせを整理したHoward Hinnantの表です。
ここで考えたいのは、「右辺値から構築できる」と「ムーブコンストラクターが宣言されている」は同じなのか、という点です。この違いを押さえると、型特性とリフレクションを何のために使い分けるかが見えてきます。
デストラクターの明示だけでムーブ宣言が抑制される理由
ムーブコンストラクターの暗黙宣言には、ユーザー宣言のデストラクターがないことなどの条件があります。~T() = default; もユーザー宣言なので、この条件を満たしません。ムーブが暗黙に宣言されない場合でも、右辺値からの構築がコピーで成立することはあります。C++作業草案のコピー/ムーブコンストラクター規則には、この点が明記されています。
次は、リフレクションを使わずに違いを確かめるC++17の例です。
#include <type_traits>
// 目的: ムーブ宣言がなくても右辺値から構築できる型を表す。
struct CopyFallback {
int value{};
// 目的: デストラクターを明示する。引数・戻り値なし。例外・外部副作用なし。
~CopyFallback() = default;
};
static_assert(std::is_copy_constructible_v<CopyFallback>);
static_assert(std::is_move_constructible_v<CopyFallback>);
どちらのアサーションも成立します。std::is_move_constructible_v<T> は、参照可能な型について T&& を引数に構築できるかを調べる型特性です。ムーブコンストラクターそのものの存在を照会するものではありません。型特性の定義に照らすと、コピーが右辺値を受け取れるこの結果も自然ですね。
掲載例はGCC 13.3.0のC++17モードでコンパイル確認しています。ここで検証したのは型特性の結果であり、C++26のリフレクション実装ではありません。
members_ofで「未宣言」と「削除済み」を分ける
そこで、式の成立可否から宣言の観察へ視点を移します。C++26向けのリフレクション提案P2996R13では、^^T によって型を表す std::meta::info を得られます。
std::meta::members_of(^^T, ctx) は、指定したアクセス文脈から参照できるメンバーのリフレクションを返します。暗黙宣言された特殊メンバーも対象ですが、データメンバーなども含むため、要素数だけを関数の数として読んではいけません。アクセス文脈や基底クラスを含めた対象範囲は、メンバー照会の仕様で確認できます。
観察の手順は、まず is_function で関数を選び、次に is_deleted で削除状態を分ける、というものです。特殊メンバーだけが目的なら is_special_member_function、ムーブコンストラクターだけなら is_move_constructor も使えます。これらはリフレクション照会として定義されています。
de Cock氏の解説本文は、小さな構造体の宣言を変えて、この区別を可視化しています。たとえばデストラクターを明示した例ではムーブ操作が一覧から消え、ムーブ操作をユーザー宣言した例では暗黙のコピー操作が削除済みとして残ります。コピー代入についても、作業草案の規則が後者の関係を定めています。
つまり、「一覧に存在しない」と「一覧にはあるが削除済み」は別の結果です。削除済みを最初から捨てずに集計すれば、使えない理由まで追えます。ただし、見えないメンバーを未宣言と判断する前に、照会のアクセス範囲をそろえる必要があります。
= defaultの意味は最初の宣言位置で変わる
宣言が見つかったら、次は誰がどのように宣言したかです。is_defaulted、is_user_declared、is_user_provided は、似ているようで異なる分類を返します。
デストラクターを例に、明示的default定義の規則に沿って整理すると、次のようになります。T はそれぞれ独立したクラスと考えてください。
| 書き方 | user-declared | user-provided | defaulted |
|---|---|---|---|
| デストラクターを書かない | false | false | true |
クラス内の初回宣言で ~T() = default; |
true | false | true |
クラス内で ~T();、クラス外で T::~T() = default; |
true | true | true |
user-provided は、単に関数本体を手書きしたという意味ではありません。ユーザー宣言され、最初の宣言で明示的にdefault化もdelete化もされていない関数が該当します。そのため、クラス外で後から = default を付けた関数は、user-provided と defaulted の両方に当てはまります。
一方、ムーブの暗黙宣言を考えるときに効くのは、デストラクターが user-declared かどうかです。表の後ろの2行はいずれも該当します。「処理をコンパイラーに任せた」と「宣言を書かなかった」を分けると、最初の例を説明できます。
リフレクションを試す環境と判定目的をそろえる
この観察を試すには、C++26というモード名だけでなく、個別機能の対応状況を見る必要があります。2026年10月27日に確認したGCCの対応表は、P2996R13のリフレクションをGCC 16、追加オプションを -freflection としています。de Cock氏の列挙コードが使う template for と define_static_array も、それぞれ別の機能として掲載されています。
一方、同日に確認したClangの対応表ではP2996R13は未対応です。試験用の派生実装と通常のコンパイラーを同一視せず、使用する版と必要な機能を合わせて選びたいところです。
実務での使い分けは、問いに合わせると明確です。「この引数で構築できるか」なら型特性を使い、「どの特殊メンバーが宣言され、削除されているか」ならリフレクションで調べます。クラスの宣言変更をレビューするときに両方の結果を並べると、呼び出し可否だけでは見えなかった理由を説明しやすくなります。
出典
- Title: C++ Reflection: Verifying Compiler-generated Functions -- Lieven de Cock
- URL: https://isocpp.org//blog/2026/10/cpp-reflection-verifying-compiler-generated-functions-lieven-de-cock
Share
Related Articles
カテゴリやタグが近い記事を続けて読めるように並べています。




