🗂

C++14 で std::enable_if で fallback つきでクラスメソッドのオーバーロードしたいメモ

2024/01/30に公開6

C++14 で, テンプレート変数を取るクラスのメソッドで, 型に応じて異なる振る舞いをしたいが, fallback(指定した型以外を処理するデフォルトメソッド) もほしい.

とりあえず std::enable_if でいけるらしいが...

enable_if で分けたいときが 3 個以上あるとき...(2 個であれば enable_if と enable_if<!> でいけるため)

テンプレートなんもわからん...


template<class T>
class Bora {
 public:
   
   // dependent type が必要なので, V をでっちあげる
   template <typename V = T, std::enable_if_t<std::is_enum<V>::value, std::nullptr_t> = nullptr>
   bool dora() {
     std::cout << "enum type\n";
     return true
   }
   
   bool dora() {
     std::cout << "fallback\n";
     return false;
   }
}

のようにしても,

enum class Muda { Ari, Ora };

Bora<Muda> eb;
eb.dora();  // enum type を期待

Bora<int> ib;
ib.dora();  // fallback を期待

どちらも fallback のほうが呼ばれてしまいます.

fallback
fallback

解決

コメントで解決方法ご教示いただきました. ありがとうございます!

以下は元記事の記録用です.

とりあえずの解決1

ネットをあさってもあんまり情報ありません.
返り値が同じだとむりぽっそ?

返り値と引数を template 型にする必要がありますが, pxrUSD のコードで見つけた,
typename を返り値と引数で受け取るようにしてやるのがとりあえずの解決でしょうか...

   // dependant type が必要なので, V をでっちあげるのは引き続き必要
   template <typename V = T>
   typename std::enable_if<std::is_enum<V>::value, T>::value
   dora(T x) {
     std::cout << "enum type\n";
     return x;
   }
   
   // T dora(T x) だとエラーになるので注意
   template <typename V = T>
   V dora(V x) {
     std::cout << "fallback\n";
     return x;
   }

とりあえずの解決2

https://stackoverflow.com/questions/61526391/c-stdenable-if-fallback

にあるように, 判定をまとめるようにして, enable_if と enable_if<!> の二つになるようにする.

C++17

C++17 なら if constexpr で解決できるでしょう.

Discussion

齊藤敦志齊藤敦志

複数用意した候補について、一方の型の制約がもう一方を「含む」ような形になる場合はオーバーロードよりも特殊化を用いるほうがやりやすいです。 完全特殊化は部分特殊化より優先され、部分特殊化はプライマリテンプレートより優先されます。

fallback として使えるプライマリテンプレートには「否定」を指示する必要がない分だけ少し楽です。

ただし、関数テンプレートは部分特殊化は出来ないという言語仕様上の制限があるために補助的なクラスを使う必要があるのが回りくどいかもしれません。

#include <iostream>
#include <type_traits>

template <class T>
class Bora {
    template <class V, class E = void>
    struct dora_helper {
        static bool dora() {
            std::cout << "fallback\n";
            return true;
        }
    };
    template <class V>
    struct dora_helper<V, typename std::enable_if<std::is_enum<V>::value>::type> {
        static bool dora() {
            std::cout << "enum type\n";
            return false;
        }
    };

  public:
    bool dora() {
        return dora_helper<T>::dora();
    }
};

int main(void) {
    enum class Muda { Ari,
                      Ora };

    Bora<Muda> eb;
    eb.dora();  // enum type を期待

    Bora<int> ib;
    ib.dora();  // fallback を期待
}
syoyosyoyo

ありがとうございます!

関数テンプレートは部分特殊化は出来ないという言語仕様上の制限

なんと!

struct 作る必要ありますが, メソッドのシグネチャは同じにできるのがいいですね.

齊藤敦志齊藤敦志

C++20 以降ならテンプレートパラメタに直接に制約を付けられるのでややこしい SFINAE の規則を使わなくてよくなりました。 従来の規則がなくなったわけではないので組み合わさると難解だったりはしますが、単純な場合にはより制約が強いほうが優先されるという感覚的にわかりやすい規則ですし、見た目にも何をしたいのかが一目瞭然です。

C++20 を導入できない事情なども色々ある場合もあるかもしれませんが、 C++20 でだいぶん楽になることは多いので可能であれば新しい機能を使いたいところです。

#include <iostream>
#include <type_traits>

template <class T>
struct Bora {
    bool dora() {
        std::cout << "fallback\n";
        return true;
    }

    bool dora()
        requires std::is_enum_v<T>
    {
        std::cout << "enum type\n";
        return false;
    }
};

int main(void) {
    enum class Muda { Ari,
                      Ora };

    Bora<Muda> eb;
    eb.dora();  // enum type を期待

    Bora<int> ib;
    ib.dora();  // fallback を期待
}
syoyosyoyo

はい. C++20 では std::format あるのも羨ましいところ.

今回は実務 OSS ライブラリのコードでの利用を想定していて, Python binding など考えると C++14(頑張れば C++17 いけるが)が限界なのですよね.

https://cibuildwheel.readthedocs.io/en/stable/cpp_standards/

実務用途では, だいたい spec の年 + 7 ~ 10 年くらいたたないと mature かつ幅広く利用可能にならなそうな気がしています. つまり C++17 がようやく使えそうになり, C++20 が実務で使えるのは 2027 ~ 2030 年...

山田(ymd)山田(ymd)

今回の件はコンパイル自体は通っていて、オーバーロード解決の優先順位の話だと思いますので、
ダミー引数を使って優先順位を操作する方法もあります。
(数値リテラルの解釈から int が優先される)

#include <iostream>
#include <type_traits>

template<class T>
class Bora {
   template <typename V = T, std::enable_if_t<std::is_enum<V>::value, std::nullptr_t> = nullptr>
   bool dora_impl(int) {
     std::cout << "enum type\n";
     return true;
   }

   bool dora_impl(char) {
     std::cout << "fallback\n";
     return false;
   }
 public:
    bool dora(){
        return this->dora_impl(0);
    }
};

enum class Muda { Ari, Ora };

int main(int argc, char** argv){
    Bora<Muda> eb;
    eb.dora();  // enum type

    Bora<int> ib;
    ib.dora();  // fallback

    return 0;
}

参考

https://yohhoy.hatenadiary.jp/entry/20190420/p1
https://qiita.com/pink_bangbi/items/023ea3b3fec28df5dcf4

syoyosyoyo

ありがとうございます!

ダミー引数必要ですが, スッキリ書けるのがいいですね. ダミー引数とってもいいようなケースではこちらで, メソッドシグネチャを変えたくないときは struct でのやり方でいきたいと思います.