2014年3月2日日曜日

SFINAEの書き方3種類

SFINAE (Substitution Failure Is Not An Error)でSubstitutionを行う場所は関数のsignatureならどこでも使えるので、以下のように3種類(template引数、関数引数、戻り値)の書き方が可能です。

#include <iostream>
#include <type_traits>
#include <string>

// 書き方1 (template引数)
template <typename T, class = typename std::enable_if<std::is_fundamental<T>::value>::type>
void f(T t) {
    std::cout << "fundamental type : " << t << std::endl;
}

template <typename T, class = typename std::enable_if<!std::is_fundamental<T>::value>::type>
void f(const T& t) {
    std::cout << "not fundamental type : " << t << std::endl;
}

// 書き方2 (関数引数)
template <typename T>
void g(T t, typename std::enable_if<std::is_fundamental<T>::value>::type* = 0) {
    std::cout << "fundamental type : " << t << std::endl;
}

template <typename T>
void g(const T& t, typename std::enable_if<!std::is_fundamental<T>::value>::type* = 0) {
    std::cout << "not fundamental type : " << t << std::endl;
}

// 書き方3 (戻り値)
template <typename T>
typename std::enable_if<std::is_fundamental<T>::value, void>::type h(T t) {
    std::cout << "fundamental type : " << t << std::endl;
}

template <typename T>
typename std::enable_if<!std::is_fundamental<T>::value, void>::type h(const T& t) {
    std::cout << "not fundamental type : " << t << std::endl;
}

int main() {
    int a = 0;
    std::string str = "string";
    f(a); // fundamental type : 0
    f(str); // not fundamental type : string
    g(a); // fundamental type : 0
    g(str); // not fundamental type : string
    h(a); // fundamental type : 0
    h(str); // not fundamental type : string
}

いずれも、std::enable_if::typeは存在、std::enable_if::typeは存在しない(falseの時に特殊化されたenable_if内でtypeが定義されていない)ことをSFINAEに利用しています。

1番目のtemplate引数に与える方法では、無名のクラスにデフォルトtemplate引数を与えています。
2番目の引数に与える方法では、enable_ifのtypeのポインター型にヌルポインターのデフォルト引数を与えています。
3番目の戻り値を使う方法では、typename std::enable_if::typeがT型であることを利用しています。

どの書き方もWebや書籍で見たことがあるので、どれを使用するかは好みでいいのだと思います。

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が使われているんですね。