2014年3月1日土曜日

SFINAEで可変個引数 (ellipsis) が使われる理由

特定の型を持っているかをbool値で返す関数を作る時に使用するSFINAE (Substitution Failure Is Not An Error) では、以下のコードのように可変個引数 (可変長引数, ellipsis)が使われることがあります。

// 下記サイトより引用
// http://debugfailure.wordpress.com/2009/10/06/understanding-sfinae/
template <typename T>
class is_container
{
    typedef char true_type;
    struct false_type{ true_type _[2]; };
    template <typename U>
    static true_type has_iterator_checker(typename U::iterator *);
    template <typename U>
    static false_type has_iterator_checker(...);
public:
    enum { value = (sizeof(has_iterator_checker<T>(0)) ==
                    sizeof(true_type)) };
};

ellipsisは型の情報を失うのでC++では邪悪なものとして扱われていますが、上記のSFINAEでは可変個引数の関数は中身を実行しないので問題ありません。 とはいえなぜellipsisにする必要があるのでしょう。 それは、関数オーバーロードの優先順位が理由です。 例えば下のようにellipsisを使用せずに書いてみます。

#include <iostream>
#include <vector>

template <typename T>
void func(const T&, typename T::iterator* = 0) {
    std::cout << "container\n";
}

template <typename T>
void func(const T&) {
    std::cout << "not container\n";
}

int main() {
    func(1);
    func(std::vector<int>());
}

この場合、func(1)は問題なく2番目のfuncが呼び出される一方、func(std::vector())の場合は両方とも関数のsignatureにマッチするので、どちらを呼び出せばいいのかわからず、"call of overloaded 'func(std::vector)' is ambiguous"というコンパイルエラーになってしまいます。

そこで必要になるのがellipsisで、これについては言語仕様で他にオーバーロード解決できる関数が無い時に限り、ellipsisにマッチする、と定められています。

N3337: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
13.3.2 Viable functionsに
A candidate function having fewer than m parameters is viable only if it has an ellipsis in its parameter list (8.3.5). For the purposes of overload resolution, any argument for which there is no corresponding parameter is considered to “match the ellipsis” (13.3.3.1.3) .

と書かれています。 そういうわけで、SFINAEではellipsisが使われているんですね。

0 件のコメント:

コメントを投稿