0Pricing
C++ Academy · レッスン

SFINAE

条件付きで関数を有効にします

「SFINAE」はCoddyKit上の無料C++ Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC++ Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C++ Academyコースには全4レッスンが含まれています。

SFINAEとは

SFINAEは「Substitution Failure Is Not An Error」の略です。コンパイラがテンプレート引数を置き換えた結果、不正な形式になった場合、その候補は重大なエラーを発生させるのではなく、単に除外されます。

  • 条件付きオーバーロードを可能にします。
  • コンパイル時選択の基盤となります。
#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
isOdd(T n) { return n % 2 != 0; }

int main() {
    std::cout << std::boolalpha << isOdd(3) << '\n';
    return 0;
}

置換失敗の実例

型を置き換えることで関数のシグネチャが不正になる場合、コンパイラはその関数を静かにスキップし、ほかの候補を調べ続けます。

#include <iostream>

template <typename T>
auto sizeOfValue(T t) -> decltype(t.size()) { return t.size(); }

int main() {
    std::string s = "hello";
    std::cout << sizeOfValue(s) << '\n';
    return 0;
}

2つのオーバーロードから1つを選ぶ

ある型に対して、ちょうど一方だけが正常に置換される2つのオーバーロードを用意します。SFINAEによって無効な方が破棄されます。

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_integral<T>::value, const char*>::type
name() { return "integral"; }

template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, const char*>::type
name() { return "floating"; }

int main() {
    std::cout << name<int>() << ' ' << name<double>() << '\n';
    return 0;
}

decltypeと式SFINAE

戻り値の型で式に対してdecltypeを使用すると、その式が型に対して有効な場合にだけ関数を存在させることができます。

#include <iostream>

template <typename T>
auto tryDouble(T x) -> decltype(x + x) { return x + x; }

int main() {
    std::cout << tryDouble(21) << '\n';
    std::cout << tryDouble(2.5) << '\n';
    return 0;
}

メンバーを検出する

典型的なSFINAEのテクニックでは、そのメンバーを使う式がコンパイルできるかを確認して、型が特定のメンバーを持つかどうかを検出します。

#include <iostream>
#include <type_traits>
#include <vector>

template <typename T>
auto hasSize(int) -> decltype(std::declval<T>().size(), std::true_type{});

template <typename T>
std::false_type hasSize(...);

int main() {
    std::cout << std::boolalpha;
    std::cout << decltype(hasSize<std::vector<int>>(0))::value << '\n';
    std::cout << decltype(hasSize<int>(0))::value << '\n';
    return 0;
}

...によるフォールバック

...(可変長引数)を受け取るオーバーロードの優先順位は最も低くなります。SFINAEで制約されたオーバーロードが失敗したときの、あらゆるケースに対応する受け皿として機能します。

#include <iostream>

template <typename T>
auto pick(int) -> decltype(T{}.value, void()) { std::cout << "has value\n"; }

template <typename T>
void pick(...) { std::cout << "no value\n"; }

struct WithValue { int value = 0; };

int main() {
    pick<WithValue>(0);
    pick<int>(0);
    return 0;
}

void_t

C++17のstd::void_tは検出を簡潔にします。正常な形式の型を任意にvoidへ変換するため、確認対象の式が有効な場合にだけ特殊化が選択されます。

#include <iostream>
#include <type_traits>
#include <vector>

template <typename T, typename = void>
struct HasSize : std::false_type {};

template <typename T>
struct HasSize<T, std::void_t<decltype(std::declval<T>().size())>> : std::true_type {};

int main() {
    std::cout << std::boolalpha;
    std::cout << HasSize<std::vector<int>>::value << '\n';
    std::cout << HasSize<int>::value << '\n';
    return 0;
}

なぜ単にエラーにしないのか

SFINAEがなければ、無効な置換は回復できないコンパイルエラーになります。SFINAEはそれを静かな「候補ではない」という状態に変えます。

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_pointer<T>::value, bool>::type
isNull(T p) { return p == nullptr; }

int main() {
    int x = 0;
    std::cout << std::boolalpha << isNull(&x) << '\n';
    return 0;
}

タグディスパッチという代替手段

SFINAEは読みにくくなることがあります。タグディスパッチでは、小さなタグ型とオーバーロード解決を使って実装を選択します。

#include <iostream>
#include <type_traits>

template <typename T>
void impl(T v, std::true_type) { std::cout << "integral: " << v << '\n'; }

template <typename T>
void impl(T v, std::false_type) { std::cout << "other: " << v << '\n'; }

template <typename T>
void handle(T v) { impl(v, std::is_integral<T>{}); }

int main() {
    handle(5);
    handle(2.5);
    return 0;
}

SFINAEとconstexpr if

C++17では、1つの関数内で分岐する場合、SFINAEの代わりにif constexprを使えることがよくあります。コンパイル時に使用されない分岐が破棄されるためです。

#include <iostream>
#include <type_traits>

template <typename T>
void describe(T v) {
    if constexpr (std::is_integral<T>::value) {
        std::cout << "integral: " << v << '\n';
    } else {
        std::cout << "other: " << v << '\n';
    }
}

int main() {
    describe(7);
    describe(3.14);
    return 0;
}

すべてを組み合わせる

SFINAEは、型の特性に基づいてオーバーロードの段階で別々の関数テンプレートから選択する必要がある場合に力を発揮します。

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_signed<T>::value, T>::type
absValue(T v) { return v < 0 ? -v : v; }

int main() {
    std::cout << absValue(-9) << '\n';
    return 0;
}

クイックチェック

SFINAEを理解できているか確認しましょう。

まとめ

SFINAEについて学びました。

  • 無効な置換が起きると、エラーにする代わりに候補が除外されます
  • enable_if、decltype、void_tと組み合わせて、関数を条件付きで有効にします
  • ...のオーバーロードがフォールバックとして機能します
  • 現代的な代替手段にはタグディスパッチとif constexprがあります

次は、従来から使われているenable_ifのパターンを詳しく学びます。

よくある質問

「SFINAE」レッスンは無料ですか?

はい。「SFINAE」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C++ Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C++ Academyコースには全4レッスンが含まれています。

「SFINAE」で何を学びますか?

条件付きで関数を有効にします ブラウザで直接実行するハンズオンコードでC++ Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C++ Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC++ Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「SFINAE」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC++ Academyレッスンでコードを書いて実行できますか?

はい。すべてのC++ Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 完全特殊化
  2. 部分特殊化
  3. SFINAE
  4. enable_ifパターン
← C++ Academyに戻る