Visual C++ で Windows UI Animation Manager を扱うとき、テンプレート基底クラス CUIAnimationCallbackBase の使い方を誤ると「argument list for class template ‘CUIAnimationCallbackBase’ is missing」という馴染みのないビルドエラーに遭遇しがちです。本記事ではエラーの正体、具体的な修正方法、設計意図(CRTP)までを整理し、再発防止チェックリストと実用的なコード例をまとめます。
症状:CUIAnimationCallbackBase の「引数が足りない」エラー
Visual C++(MSVC)で次のようなエラーが出ます。
argument list for class template 'CUIAnimationCallbackBase' is missing
典型的には、イベントハンドラの生成で静的メソッドを呼び出す箇所が原因です。
// NG: テンプレート引数を書かずに静的メンバを呼び出している
*hr = CUIAnimationCallbackBase::CreateInstance(
ppManagerEventHandler,
&*aniManEventHandler);
原因:クラス“テンプレート”であることを忘れている
CUIAnimationCallbackBase はクラス“テンプレート”です。したがって型として参照するたびに、テンプレート実引数を明示しなければなりません。クラス名を修飾子として静的メンバ(CreateInstance など)を呼ぶときも同様です。関数テンプレートのように引数からの型推論は行われません。C++17 の CTAD(Class Template Argument Deduction)も、変数初期化など限定的な場面でのみ働き、今回のような「修飾名経由の静的メンバ呼び出し」には効きません。
| 現象 | 根本原因 | 一言メモ |
|---|---|---|
| 「argument list … is missing」 | クラステンプレートに実引数を与えずに静的メンバを参照 | 関数テンプレートと違い推論されない |
| MSVC C2955 エラー | 同上(クラス テンプレートの使用にはテンプレート引数が必要) | エラーメッセージが英語でも意味は同じ |
正しい修正:テンプレート引数を明示する
CUIAnimationCallbackBase は「実装したいインターフェイス型」と「派生クラス型(自分自身)」の 2 つを引数に取る CRTP(Curiously Recurring Template Pattern)方式の基底クラスです。呼び出し時に必ず両方を指定します。
// OK: テンプレート引数を明示する
*hr = CUIAnimationCallbackBase<
IUIAnimationManagerEventHandler, // 実装したいインターフェイス
animationManagerEventHandler // 呼び出し元の派生クラス(自分自身)
>::CreateInstance(
ppManagerEventHandler,
&*aniManEventHandler);
この修正でコンパイラが型を具体化できるようになり、エラーは解消されます。
背景知識:なぜ CRTP が使われるのか
UI Animation Manager の各種イベント ハンドラ(IUIAnimationManagerEventHandler など)は COM インターフェイスです。CUIAnimationCallbackBase はそれらインターフェイス実装の共通処理(IUnknown の AddRef/Release/QueryInterface など)をテンプレートで汎用化し、派生クラスの型をテンプレート引数として受け取ることで、静的ポリモーフィズムを用いつつキャストやファクトリを安全にまとめています。これが CRTP の典型的な使い方です。
CRTP のイメージ
template<class TInterface, class TDerived>
class CUIAnimationCallbackBase : public TInterface {
public:
static HRESULT CreateInstance(TInterface** ppIface, TDerived** ppRaw);
// IUnknown 実装や共通ユーティリティをここに…
};
ここで TDerived は 自分自身 の型です。派生クラスは次のように定義します。
class animationManagerEventHandler final
: public CUIAnimationCallbackBase<IUIAnimationManagerEventHandler,
animationManagerEventHandler>
{
public:
// IUIAnimationManagerEventHandler のメソッドを実装
HRESULT STDMETHODCALLTYPE OnManagerStatusChanged(
UI_ANIMATION_MANAGER_STATUS newStatus,
UI_ANIMATION_MANAGER_STATUS previousStatus) noexcept override
{
// 任意の処理
return S_OK;
}
};
呼び出し側の完全例(安全なポインタ取り扱いを含む)
実運用では、派生クラスの生成と同時にインターフェイス ポインタを受け取りたいことが多いでしょう。安全性・可読性のためにエイリアスとスマートポインタを併用します。
#include <UIAnimation.h>
// 例: Microsoft::WRL::ComPtr を使う場合
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
// エイリアスで可読性を上げる
using ManagerHandlerBase =
CUIAnimationCallbackBase<IUIAnimationManagerEventHandler,
animationManagerEventHandler>;
// どこかの生成関数
HRESULT CreateManagerEventHandler(
IUIAnimationManagerEventHandler** ppManagerEventHandler,
animationManagerEventHandler** ppRawDerived) noexcept
{
if (!ppManagerEventHandler || !ppRawDerived) return E_POINTER;
// CreateInstance は静的メソッド。テンプレート引数は省略不可
return ManagerHandlerBase::CreateInstance(ppManagerEventHandler, ppRawDerived);
}
// 使う側(WRL の ComPtr を前提にした例)
HRESULT Setup()
{
ComPtr<IUIAnimationManagerEventHandler> managerHandler;
animationManagerEventHandler* rawDerived = nullptr;
HRESULT hr = ManagerHandlerBase::CreateInstance(managerHandler.GetAddressOf(), &rawDerived);
if (FAILED(hr)) return hr;
// rawDerived は所有権の取り扱いに注意(CreateInstance の契約に従う)
// 必要なら ComPtr に包む
ComPtr<animationManagerEventHandler> derivedHolder;
derivedHolder.Attach(rawDerived);
// 以降、managerHandler(インターフェイス)を UIAnimationManager に登録するなど
return S_OK;
}
ポイントは次の通りです。
- テンプレート引数は必須(
ManagerHandlerBaseのusingで一度だけ書く)。 - スマートポインタの
GetAddressOf()/&を適切に使う。&*aniManEventHandlerのような書き方は読みにくく、型が変わると壊れやすいので避けるのが無難です。 - 所有権の移譲規約(
CreateInstanceが返す生ポインタをAttachで包む、など)を守る。
よくある落とし穴と対策
| 落とし穴 | 症状 | 対策 |
|---|---|---|
| テンプレート引数を書き忘れる | 今回のビルドエラー | using Base = CUIAnimationCallbackBase<...> で別名化 |
| 派生クラス名の不一致 | TDerived に別の型を渡してしまい static_cast が破綻 | コピペ時にクラス名を必ず見直す |
| スマートポインタの誤用 | &*ptr など可読性の低いアドレス取得 | GetAddressOf()(WRL)、operator&(ATL)、out_ptr(C++23)を使う |
HRESULT をスマートポインタで包む | メモリ管理がむしろ複雑化 | HRESULT は値型。スタック変数で十分 |
間違いを再現する最小コードと、正しい最小コード
NG(エラーを再現)
class animationManagerEventHandler; // 前方宣言
void foo(IUIAnimationManagerEventHandler** ppManagerEventHandler,
animationManagerEventHandler** ppRaw)
{
// ✗ クラス名だけで静的メンバを呼ぶ(テンプレート引数がない)
auto hr = CUIAnimationCallbackBase::CreateInstance(ppManagerEventHandler, ppRaw);
(void)hr;
}
OK(最小修正版)
class animationManagerEventHandler; // 前方宣言
void foo(IUIAnimationManagerEventHandler** ppManagerEventHandler,
animationManagerEventHandler** ppRaw)
{
// 〇 必ずテンプレート引数を与える
auto hr = CUIAnimationCallbackBase::
CreateInstance(ppManagerEventHandler, ppRaw);
(void)hr;
}
派生クラスの実装サンプル(イベントハンドラ)
UI Animation Manager の代表的なコールバック IUIAnimationManagerEventHandler を実装します。必要最小限のメソッドだけ示します。
class animationManagerEventHandler final
: public CUIAnimationCallbackBase<IUIAnimationManagerEventHandler,
animationManagerEventHandler> {
public:
// 状態遷移の通知
HRESULT STDMETHODCALLTYPE OnManagerStatusChanged(
UI_ANIMATION_MANAGER_STATUS newStatus,
UI_ANIMATION_MANAGER_STATUS prevStatus) noexcept override
{
// ログなど
return S_OK;
}
```
// 必要に応じて他のメソッドも実装
// HRESULT STDMETHODCALLTYPE OnManagerStatusIntermediate(...) override { ... }
```
};
ビルドの実務ポイント
- ヘッダー:
<UIAnimation.h>をインクルード。 - 初期化:COM を
CoInitializeEx(またはRoInitialize)で初期化。 - リンカ:必要に応じて関連ライブラリをリンク(プロジェクト構成による)。
- 例外:UI Animation API は
HRESULTベース。例外を使わない方針ならRETURN_IF_FAILEDなどのマクロ/ユーティリティを用意。
可読性と保守性を向上させるテクニック
テンプレート実引数が長いときは別名で短縮
using ManagerHandlerBase =
CUIAnimationCallbackBase<IUIAnimationManagerEventHandler,
animationManagerEventHandler>;
HRESULT init(IUIAnimationManagerEventHandler** pp)
{
animationManagerEventHandler* raw = nullptr;
return ManagerHandlerBase::CreateInstance(pp, &raw);
}
スマートポインタの選択肢
| 選択肢 | 特徴 | アドレス取得 |
|---|---|---|
WRL Microsoft::WRL::ComPtr | ヘッダーのみ、モダン C++ で使いやすい | ptr.GetAddressOf() / ptr.ReleaseAndGetAddressOf() |
ATL CComPtr | 古参。ATL 依存を許容するなら強力 | &ptr(I** を直接渡せる) |
WIL wil::com_ptr | 安全ユーティリティ豊富、軽量 | ptr.put() / ptr.put_void() |
HRESULT をスマートポインタで包まない
HRESULT は単なる数値(値型)です。unique_ptr<HRESULT> のようにヒープ確保しても安全性は上がりません。ローカル変数で十分です。例:
HRESULT hr = S_OK;
hr = ManagerHandlerBase::CreateInstance(...);
if (FAILED(hr)) { /* エラー処理 */ }
デバッグ手順:コンパイルからリンクまでのチェックリスト
- エラー行のクラス名にテンプレート実引数があるか(
<I..., 派生型>)。 using Base = ...で別名化して再利用しているか。- 派生クラス名(
animationManagerEventHandler)をTDerivedに渡しているか。 - イベント インターフェイス(
IUIAnimationManagerEventHandlerなど)が正しいか。 - スマートポインタの アドレスの渡し方 が適正か(
GetAddressOf()/put())。 - COM の初期化(
CoInitializeEx)が先に行われているか。 - リンカ設定が正しいか。
FAQ
Q. なぜ関数テンプレートみたいに引数から推論してくれないの?
A. 今回は「クラス テンプレートの 静的メンバ」を 修飾名 で呼んでいます。クラス テンプレート部分は引数推論の対象外で、明示が必須です。CTAD も適用外です。
Q. &*aniManEventHandler は間違い?
A. コンパイラ的には動く場合がありますが、読み手には意図が伝わりにくく、所有権を誤解させます。スマートポインタ固有の out-parameter API(GetAddressOf、put() など)を使う方が安全・明瞭です。
Q. テンプレート引数の順番は固定?
A. はい。<インターフェイス, 派生クラス> の順で設計されています(名前から推測できないときは定義を確認しましょう)。
Q. エイリアスはどこに置くべき?
A. ヘッダー(派生クラス定義の近く)に置くと再利用性と可読性が高まります。namespace detail に隠すのも手です。
再発防止のための命名と構造化のコツ
- 別名は「Base」「HandlerBase」など役割が分かる名前にする。
- 派生クラスは
finalを付け、インターフェイスに対してだけ公開する。 - ファクトリ関数(
CreateInstance呼び出し)を 1 箇所に集約し、アドレスの受け渡し規約を統一する。
関連するコンパイル/設計エラーの早見表
| メッセージ例 | 意味 | 対処 |
|---|---|---|
| C2955: use of class template requires template argument list | クラス テンプレートに実引数がない | <I..., 派生型> を付ける |
| C2027: use of undefined type ‘animationManagerEventHandler’ | 派生クラスの前方宣言やインクルード不足 | 定義の順序を見直す/ヘッダーを追加 |
| C3668: method with override specifier ‘…’ did not override any base class methods | インターフェイスのシグネチャが一致していない | 引数型や noexcept、STDMETHODCALLTYPE を確認 |
まとめ
CUIAnimationCallbackBaseはクラス テンプレート。毎回テンプレート引数を明示する。- 設計は CRTP。第 1 引数にインターフェイス型、第 2 引数に派生クラス型を与える。
- 静的メソッド呼び出し時(
::CreateInstance)も 推論されない点に注意。 - スマートポインタは各ライブラリの out-parameter API を使い、
&*ptrは避ける。 - エイリアス (
using) で簡潔にし、読みやすさと保守性を高める。
実務向けテンプレート:そのまま使える雛形
最後に、記事のポイントをすべて盛り込んだ雛形を掲載します。プロジェクトに合わせて必要な #include やリンク設定を追加してください。
// ---- Animation Manager Event Handler Boilerplate ----
// 必要なヘッダー(例)
#include <UIAnimation.h>
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
// 前方宣言
class animationManagerEventHandler;
// テンプレート基底の別名(忘れない!)
using ManagerHandlerBase =
CUIAnimationCallbackBase<IUIAnimationManagerEventHandler,
animationManagerEventHandler>;
// 派生クラス(イベントハンドラ本体)
class animationManagerEventHandler final
: public ManagerHandlerBase
{
public:
animationManagerEventHandler() = default;
// IUIAnimationManagerEventHandler
HRESULT STDMETHODCALLTYPE OnManagerStatusChanged(
UI_ANIMATION_MANAGER_STATUS newStatus,
UI_ANIMATION_MANAGER_STATUS prevStatus) noexcept override
{
// 実装
return S_OK;
}
// 他のメソッドも必要に応じて…
};
// 生成関数
inline HRESULT MakeAnimationManagerEventHandler(
IUIAnimationManagerEventHandler** ppIface,
animationManagerEventHandler** ppRawDerived) noexcept
{
if (!ppIface || !ppRawDerived) return E_POINTER;
return ManagerHandlerBase::CreateInstance(ppIface, ppRawDerived);
}
// 使用例
inline HRESULT SetupAnimationCallbacks()
{
ComPtr<IUIAnimationManagerEventHandler> iface;
animationManagerEventHandler* raw = nullptr;
// CreateInstance を呼ぶときもクラス テンプレート名は別名経由で確定済み
RETURN_IF_FAILED( ManagerHandlerBase::CreateInstance(iface.GetAddressOf(), &raw) );
ComPtr<animationManagerEventHandler> derived;
derived.Attach(raw);
// ここで Animation Manager に登録などの処理…
return S_OK;
}
この雛形では、次のことを徹底しています。
- テンプレート実引数の明示(
using ManagerHandlerBase = ...)。 - スマートポインタの
GetAddressOf()とAttach()で所有権を明確化。 - COM らしい
HRESULTベースの制御フローを維持。
チェックポイント(コピー前の最終確認)
CUIAnimationCallbackBase<I..., 派生型>の 2 つの引数を忘れていないか。- 派生クラス名の綴り(大文字小文字含む)は正しいか。
- アドレス渡しにスマートポインタの正規 API を使っているか。
- イベントハンドラのメソッド シグネチャがヘッダーと一致しているか。
以上で、CUIAnimationCallbackBase の「テンプレート引数が足りない」エラーの原因と解決、そして安全な使い方までを一通り押さえられます。既存コードのリファクタリング時は、まず using エイリアス化から始めるのが最も効果的です。
設計メモ:CreateInstance の契約を確認する
CreateInstance が返すポインタの所有権(誰が Release するか)、失敗時の未初期化保証、再入不可条件など、関数の契約は基底クラス側の実装に依存します。プロジェクト共通の「生成関数」を 1 箇所に集約し、全呼び出し元が同じ規約に従うようにしておくと、後からスマートポインタの種類を変更しても影響を最小化できます。
設計メモ:テストしやすさのための分離
アニメーションのロジックと UIAnimation のコールバック層を分離し、コールバック層から独立したユニットテストが書ける構造にしておくと保守性が飛躍的に向上します。CRTP はコンパイル時に型を確定させるため、インターフェイス越しの仮想呼び出しより高速で、かつ共通実装の重複も避けられます。
要点整理:「CUIAnimationCallbackBase はクラス テンプレート。静的メソッドを呼ぶときもテンプレート引数は必須」。この 1 行をチームのコーディング規約に明記しておくと、同種の不具合が激減します。

コメント