嗚呼、帰りたい

プログラミングのことから日常のことまで。いわゆるごった煮というものだな。

C++で型に特定のメンバ関数が宣言されているときだけその関数を呼び出す処理を実装する

お久しぶりです,帰宅志願者です.この記事はあくあたん工房 Advent Calendar 2020の13日目の記事です.なお執筆時点で14日目になっていて大遅刻しております.ごめんなさい.前置きはこれぐらいにして本題へ.

型に特定のメンバ関数が宣言されている場合はその関数を呼び出したい...

と思うことが最近C++を書いていて思うことがありました.これを実現するには

  1. まず型に特定のメンバ関数が存在するかを確認し
  2. 存在しているかどうかで処理を分岐する

ことが必要です.私はC++初心者なので頭をひねってみてもどのようにすれば良いのかがわかりません.というかそもそもこれが実現できるのかさえ怪しく思えます.

ということでGoogle先生に聞いてみた.

型に特定のメンバ関数が存在するかを確認する

SFINAEを使うバージョン

最初に出てきたのがSFINAEを用いたこちらの形.型Tにfunc()というメンバ関数が存在するかどうかを調べています.

template<typename T>
struct has_func {
private:
    template<typename U, typename = std::void_t<decltype(std::declval<U>().func())>>
    static std::true_type check(U);
    static std::false_type check(...);
public:
    static constexpr bool value = decltype(check(std::declval(T)))::value;
};

以下のようにして型に特定の関数が存在するかをコンパイル時にbool値で得ることができます.

has_func<T>::value;

メタ関数を使うバージョン

最初の結果で満足せずにGoogle先生を質問攻めにしたところ std::experimental::is_detected なるものが出てきました.これはまだC++で実験的な扱いを受けているメタ関数ですが,これを使うと次のようにしてメンバ関数の存在を調べることができます.

template<typename T>
using func_t = decltype(std::declval<T>().func());

template<typename T>
using has_func = std::experimental::is_detected<func_t, T>;

こちらも先ほどと同様に以下のようにして特定の関数が存在するかをコンパイル時にbool値で得ることができます.

has_func<T>::value;

SFINAEを用いたバージョンと比較してとてもスッキリとした印象を受け,良さげに思えます.しかしながら, std::experimental::is_detected はまだ実験的なものですから使用が難しいところでしょう.幸いなことに std::experimental::is_detected は数行の実装で実現することができます.そこで以下のように実装してしまうことにしましょう.

namespace detail {
    template<typename AlwaysVoid, template<typename ...> typename Op, typename ...Args>
    struct detector : std::false_type {};

    template<template<typename ...> typename Op, typename ...Args>
    struct detector<std::void_t<Op<Args...>>, Op, Args...> : std::true_type {};
}

template<template<typename ...> typename Op, typename ...Args>
using is_detected = detail::detector<void, Op, Args...>;

あるいは自分で実装しなくてもBoost.TypeTraitsがすでに実装を提供していますから,そちらを使っても良いと思います.

メンバ関数が存在しているかどうかで処理を分岐する

先程まででメンバ関数が存在しているかどうかがbool値で得られることがわかったのでこれを使って実装します.やり方としては愚直にif文を使ったりSFINAEを使う方法がありますが,もう2020年も終わろうとしている時期ですからC++17の恩恵を被ることにしましょう.constexpr if文を使います.

T t;
if constexpr (has_func<T>::value) t.func();

if文と同様の記述量でSFINAEと同様に実体化するかどうかの制御をすることができます.清々しいほどにシンプルですね.

まとめ

C++でここまで込み入ったコードは初めて書きましたが,静的型付け言語であるC++でこんな処理ができるのかと驚くばかりでした.私はC++初心者なので(保険)記事に間違いや気になる点などがありましたら優しく指摘していただけると幸いです.以上「C++で型に特定のメンバ関数が宣言されているときだけその関数を呼び出す処理を実装する」でした.