[C++] 如何にしてエイリアスを作るか

 ここのとこ寒くなってきましたね。体調崩していませんか?僕は崩しました。お仕事がそこそこ忙しく、月一更新とはどこ吹く風ですが、少し時間が出来たので記事数を稼いでおこうかと思います。さて、今回はC++で型や変数、関数等のエイリアスをどうやって作るかをまとめてみたいと思います。

エイリアスを定義するのは意外と面倒

 物によるんですが、C++でエイリアスを定義するのは意外と面倒だったりします。型なら簡単なんですけどね。というわけで、型、関数、変数についてそれぞれエイリアスを作ってみたいと思います。

型のエイリアス

 まずは一番簡単な型のエイリアスから。皆さんご存知のtypedefさんが活躍します。

typedef int type;

 こんな感じで"int"の別名を"type"にすることが出来ます。

// int a = 10; と同じ
type a = 10;

 こうやって使えます。C++11からは"using"を使っても同じようなことが出来ます。というより、こっちを使うほうが推奨されています。

using type = int;

 この時、"typedef"とは定義順が逆になるのはちょっと注意。

 また、"using"を使うと、templateクラスの別名の定義が楽になります。というのも、"typedef"ではテンプレート引数の一部を指定したテンプレートクラスを定義することが出来ないからです。

template<typename T>
typedef std::map<int, T> IntKeyMap; // エラー!

 こんな書き方は出来ません。出来ませんでしたが、C++11からは"using"を使って

template<typename T>
using IntKeyMap = std::map<int, T>;

のような書き方ができるようになりました。C++98まではマクロを使って

#define IntKeyMap(T) std::map<int, T>
IntKeyMap(std::string) hoge;

のようなドロドロした書き方をするか、Type Generatorというテクニックを使って

template<typename T>
struct IntKeyMap
{
    typedef std::map<int, T> type;
};

IntKeyMap<std::string>::type hoge;

のように書くことが出来ます。

関数ポインタ型のエイリアス

 次は関数ポイン型のエイリアスについてざっとまとめたいと思います。C言語の闇文法の一つに、この関数ポインタ型のエイリアス定義があります。

typedef size_t (*Func)(const char *);

ってやつですね。あー闇々しい。こうすることで、

Func hoge = strlen;
size_t len = hoge("HAGE");

 みたいに書けるようになります。この複雑な文法、実は簡単に覚えることが出来て、普通の関数定義の関数名の部分がそのまま関数ポインタの型になります。

// 普通の関数定義
size_t strlen(const char *);
// 関数ポインタ型
size_t (*Func)(const char *);

 ね、簡単。ただ、"const"をつけようとするとさらに厄介なことになります。

typedef const char* type;

な勢いで

typedef const size_t (* Func)(const char *);

みたいにやってしまうと、戻り値が"const size_t"の関数ポインタ型を定義していることになるので、意図したエイリアスにはなりません。

typedef size_t (*const Func)(const char *);

と書く必要があります。もっとも、typedefでconstを使うことはないと思いますが、後述の関数のエイリアスを定義する際に重要になってくるので覚えておいて損はありません。
 ちなみに、"using"を使うと既存の"using"と同等の文法でエイリアスを定義できるので、C++11を使う際はこちらを使うといいと思います。

using Func = size_t (*)(const char *);

関数のエイリアス

 お次は型ではなく関数のエイリアスを定義する方法を考えてみましょう。関数は、クラスに対するインスタンスのようなものです。つまり、型ではなく実体である、ということです。型ではないので"typedef"でエイリアスを定義することは出来ません。C++0x策定段階では、"using"を用いて関数のエイリアスを定義する方法が提案されていましたがボツになったようです。C++98ではそもそも関数のエイリアスを定義することはできません(もちろんC++11でも、ですが)。したがって、いくつかの代案を考える必要があります。

inline展開を使った関数エイリアス

 C++における最も簡単で一般的なエイリアスの作り方はこれだと思います。エイリアスを定義したい関数を呼ぶだけの関数を定義します。

inline size_t hoge(const char *str)
{
    return strlen(str);
}

のような記述です。最適化がかかればstrlenと同等の呼び出しになりますし、賢いコンパイラなら整数の定数に置き換えてくれることもあります。
 この方法は多少面倒ではありますが、IDEで引数の補完が効くので他の方法に比べ扱いやすいと言えます。一方でデメリットも存在します。それは、可変長引数を扱えない、という点です。

inline int hoge(const char *format, ...)
{
    return printf(format, /* ここが書けない… */);
}

 一度va_listに展開しなければならないため、そのままの形で展開させるのは難しいでしょう。賢いコンパイラならそのへんまでやってくれるのかもしれませんが…未検証です。

マクロを使った関数エイリアス

 C言語ではお馴染みの方法ですね。

#define hoge(F, ...) printf(F, ##__VA_ARGS__)

 このような書き方をすることで、inlineを使った書き方では出来なかった可変長引数関数への対応も可能です。デメリットとして、引数のコード補完がうまく機能しない点、名前空間に縛ることができないため、名前の重複が起こった際にどうしようもなくなってしまう点、悪に魂を売ってしまっている点などが挙げられます。もっとも、C++を使っているのにマクロなんていう機能を使っている方は滅ぶべきだとは思いますが。

variadic template+perfect forwardingを用いた関数エイリアス

 こちらはC++11限定の方法ですが、variadic template(可変長テンプレート引数)を用いた方法があります。

template<typename... Args>
inline auto hoge(Args&&... args) -> decltype(strlen(std::forward<Args>(args)...))
{
    return strlen(std::forward<Args>(args)...);
}

こんな感じです。いきなり複雑な感じになっていますが、重要なのは

  • 戻り値の型はエイリアスの対象の戻り値の型から推定する
  • 可変長テンプレート引数で任意個の引数を受け取る
  • std::forwardを用いて引数の転送を行う

の3点です。1つ目の戻り値の話は、予めエイリアス対象の型を指定してしまっても問題ないため、あまり重要ではないかもしれません。邪悪なマクロを使って悪に魂を売ればエイリアスの定義が簡単になります。

#define DEFINE_ALIAS(name, target)  \
    template<typename... Args>      \
    inline auto name(Args&&... args) -> decltype(target(std::forward<Args>(args)...))   \
    { return target(std::forward<Args>(args)...);   }

DEFINE_ALIAS(hoge, strlen);
DEFINE_ALIAS(hage, printf); // 可変長引数もいけちゃう

 戻り値の型を後置指定できるので、こんなに簡単に書けます。デメリットは特にありません。あるとすれば、エイリアスはテンプレート関数になるので、IDEによるコード補完が効かなくなる点、型を間違うとエラーを追いにくくなる点、C++11でしか使えない点、等でしょうか。そこまで致命的な欠点はありません。

関数ポインタを使った関数エイリアス

 もっとも扱いやすく定義しやすい方法でエイリアスを作るのはこの方法になると思います。表題の通り、関数ポインタを使ってエイリアスを定義する方法です。

static size_t (*const hoge)(const char *) = strlen;

 こんな書き方です。関数ポインタといえば圧倒的に遅い関数呼び出し方法で有名なので心配になる方も多いかと思いますが、"static const"を用いているのでコンパイラは単純にもとの関数呼び出しに置き換えることが出来るため、

const size_t len = hoge("HAGE");
const size_t len = strlen("HAGE");

のどちらも同じ命令が出力されます。
 この方法なら、C++98やC言語でも扱うことができ、マクロを用いることもなく、特定の名前空間に縛ることも可能です。また、エイリアスは関数ポインタ型なので、コード補完時に若干読みにくくはなりますがしっかり補完されます。

コンパイラ拡張を用いた関数エイリアス

 最後に、「真の意味で関数のエイリアスを定義する方法です。C++の言語仕様として関数のエイリアスが定義できなくなったとはいえ、やはりエイリアスを定義したいという需要は少なくありません。そんな人達のために、コンパイラ拡張で定義する方法があります。
 GCCでは以下の方法でエイリアスを定義できます。

size_t hoge(const char *) __attribute__((weak, alias("strlen"));

 "__attribute__"を使用してエイリアスの定義を行います。clangでも同等の方法でできると思ったのですが、どうやらDarwinではweak aliasが認められていないようでした。

 MSVC(VC++)でのやり方はイマイチ分かっておりません。あるのかどうかもわかりません。

#pragma comment(linker, "/export:hoge=strlen")

でリンカにオプションを渡すことで出来なくもなさそうですが、はたしてこのコメントはこういう使い方をするためにあるのかどうか…

変数のエイリアス

 多くは語りません。参照を使ってください。

まとめ

 以上が型、関数、変数のエイリアスの作り方の一例の紹介でした。他にももっと書きやすい方法があるかもしれませんし、今後の仕様改定で取り入れられるかもしれません。エイリアスくらいサクッと定義させてほしいものですね…

 ちなみになんでこんなネタで記事を書いたのかというと、マイクロなんちゃらのなんちゃらドウズのライブラリに_tprintfとかがいるせいです。環境によってprintf(可変長引数な関数)使い分けたい時どうするんだよ、と思ったのがきっかけ。

1 件のコメント :

  1. >C++を使っているのにマクロなんていう機能を使っている方は滅ぶべきだとは思いますが
    こういう攻撃的で誤解を招く言い方はしない方がいいですよ
    それこそ環境によって使うコードを変えるなど、プリプロセッサ無しでどうやるんだよって話。
    まぁWindowsもさっさとutf-8を標準にして欲しいですけどね

    返信削除