Visual C++のCUIAnimationCallbackBaseで「argument list for class template is missing」エラーを解決する完全ガイド

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 はそれらインターフェイス実装の共通処理(IUnknownAddRef/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;
}

ポイントは次の通りです。

  • テンプレート引数は必須ManagerHandlerBaseusing で一度だけ書く)。
  • スマートポインタの 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 依存を許容するなら強力&ptrI** を直接渡せる)
WIL wil::com_ptr安全ユーティリティ豊富、軽量ptr.put() / ptr.put_void()

HRESULT をスマートポインタで包まない

HRESULT は単なる数値(値型)です。unique_ptr<HRESULT> のようにヒープ確保しても安全性は上がりません。ローカル変数で十分です。例:

HRESULT hr = S_OK;
hr = ManagerHandlerBase::CreateInstance(...);
if (FAILED(hr)) { /* エラー処理 */ }

デバッグ手順:コンパイルからリンクまでのチェックリスト

  1. エラー行のクラス名にテンプレート実引数があるか(<I..., 派生型>)。
  2. using Base = ... で別名化して再利用しているか。
  3. 派生クラス名(animationManagerEventHandler)を TDerived に渡しているか。
  4. イベント インターフェイス(IUIAnimationManagerEventHandler など)が正しいか。
  5. スマートポインタの アドレスの渡し方 が適正か(GetAddressOf() / put())。
  6. COM の初期化(CoInitializeEx)が先に行われているか。
  7. リンカ設定が正しいか。

FAQ

Q. なぜ関数テンプレートみたいに引数から推論してくれないの?
A. 今回は「クラス テンプレートの 静的メンバ」を 修飾名 で呼んでいます。クラス テンプレート部分は引数推論の対象外で、明示が必須です。CTAD も適用外です。

Q. &*aniManEventHandler は間違い?
A. コンパイラ的には動く場合がありますが、読み手には意図が伝わりにくく、所有権を誤解させます。スマートポインタ固有の out-parameter API(GetAddressOfput() など)を使う方が安全・明瞭です。

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インターフェイスのシグネチャが一致していない引数型や noexceptSTDMETHODCALLTYPE を確認

まとめ

  • CUIAnimationCallbackBase はクラス テンプレート。毎回テンプレート引数を明示する。
  • 設計は CRTP。第 1 引数にインターフェイス型、第 2 引数に派生クラス型を与える。
  • 静的メソッド呼び出し時(::CreateInstance)も 推論されない点に注意。
  • スマートポインタは各ライブラリの out-parameter API を使い、&*ptr は避ける。
  • エイリアス (using) で簡潔にし、読みやすさ保守性を高める。

実務向けテンプレート:そのまま使える雛形

最後に、記事のポイントをすべて盛り込んだ雛形を掲載します。プロジェクトに合わせて必要な #include やリンク設定を追加してください。

// ---- Animation Manager Event Handler Boilerplate ----
// 必要なヘッダー(例)
#include &lt;UIAnimation.h&gt;
#include &lt;wrl/client.h&gt;

using Microsoft::WRL::ComPtr;

// 前方宣言
class animationManagerEventHandler;

// テンプレート基底の別名(忘れない!)
using ManagerHandlerBase =
    CUIAnimationCallbackBase&lt;IUIAnimationManagerEventHandler,
                             animationManagerEventHandler&gt;;

// 派生クラス(イベントハンドラ本体)
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&lt;IUIAnimationManagerEventHandler&gt; iface;
    animationManagerEventHandler* raw = nullptr;

    // CreateInstance を呼ぶときもクラス テンプレート名は別名経由で確定済み
    RETURN_IF_FAILED( ManagerHandlerBase::CreateInstance(iface.GetAddressOf(), &amp;raw) );

    ComPtr&lt;animationManagerEventHandler&gt; 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 行をチームのコーディング規約に明記しておくと、同種の不具合が激減します。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次