Home / ぼやきごと / 2015-02-06
2015-02-06

C++CLI: template 型引数にマネージ型を渡す

C++/CLIでは当然ながら template を記述できますが、その型引数にはマネージ型も渡すことができます。
C++11の Variadic templates 機能をサポートしているVC++2013では、次のようなコードも書くことができます。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 
 
 
-
-
!
|
-
|
!
|
-
!
|
-
|
!
|
-
!
|
-
|
!
!
 
 
-
-
!
|
-
!
|
|
-
!
|
|
|
|
!
using namespace System;
 
namespace
{
    // 引数をコンストラクタに渡して型 T のオブジェクトを生成する。
    template<class T, class ... TArgs>
    T^ Create(TArgs ... args)
    {
        return gcnew T(args...);
    }
 
    // (a + b) を返す。
    template<class T, class U>
    auto Add(T a, U b) -> decltype(a + b)
    {
        return (a + b);
    }
 
    // (a + b + ...) を返す。
    template<class T, class U, class ... TArgs>
    auto Add(T a, U b, TArgs ... args) -> decltype(Add(a + b, args...))
    {
        return Add(a + b, args...);
    }
} // namespace
 
int main(cli::array<String^>^ args)
{
    // 文字配列作成
    auto test = gcnew cli::array<wchar_t>{ L't', L'e', L's', L't' };
 
    // String のコンストラクタ引数を渡してオブジェクト生成
    auto a = Create<String>(test);
    auto b = Create<String>(L'!', 3);
 
    // (a + b + L' ' + DateTime::Now) と同義
    auto str = Add(a, b, L' ', DateTime::Now);
 
    Console::WriteLine(str);
 
    return 0;
}

C++的にはそれほど驚きの無いコードですが、C#のジェネリック(generic)では実現できない次のような処理をあっさりと書けてしまっています。

  • 引数付きコンストラクタ呼び出しによるオブジェクト生成。
  • template 型同士の演算。

しかも template であるため関数の実体化はコンパイル時に行われ、もし引数を間違えていてもコンパイルエラーによって気付くことができます。

上述のコードをC#で実現しようとすると次のようなコードになるでしょう。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 
 
 
-
|
-
-
|
|
!
-
|
!
|
-
|
|
!
-
|
|
-
|
!
|
!
|
-
|
|
!
-
|
|
|
|
|
|
|
|
|
|
!
!
!
using System;
 
namespace Sample
{
    static class Program
    {
        /// <summary>
        /// 引数をコンストラクタに渡して型 T のオブジェクトを生成する。
        /// </summary>
        static T Create<T>(params object[] args)
        {
            return (T)Activator.CreateInstance(typeof(T), args);
        }
 
        /// <summary>
        /// (a + b + ...) を返す。
        /// </summary>
        static dynamic Add(dynamic a, dynamic b, params dynamic[] args)
        {
            dynamic r = a + b;
            foreach (var arg in args)
            {
                r += arg;
            }
            return r;
        }
 
        /// <summary>
        /// メインエントリポイント。
        /// </summary>
        static int Main(string[] args)
        {
            var test = new[] { 't', 'e', 's', 't' };
 
            var a = Create<string>(test);
            var b = Create<string>('!', 3);
 
            var str = Add(a, b, ' ', DateTime.Now);
 
            Console.WriteLine(str);
 
            return 0;
        }
    }
}

下記の代替手段を利用しています。

  • ジェネリック型に対して引数付きコンストラクタを呼び出すことはできないため、 Activator.CreateInstance メソッドを利用。
  • ジェネリック型同士の演算は無理、かつ戻り値の型を推定できないため、 dynamic を利用。

「代替手段があるならいいんじゃないの?」と思うかもしれませんが、これらの手段ではもし引数に渡す値が間違っていてもコンパイルが通ってしまい、実行時例外が起きるまで間違いに気付くことができません。
安全性の面で言えば、コンパイル時にミスを検出できるC++/CLI側のコードに軍配が上がるでしょう。

C++/CLIというと「C++の資産(ライブラリ)をラップするための言語」という認識の人が多いですし、それも間違いではないのですが、「マネージ型主体のコードを書きつつピンポイントで template 等のC++機能を利用する」という使い方をしてみてもいいかもしれません。

Category: [C++][C#][Visual Studio][プログラミング] - 2015-02-06 02:35:31