とくにあぶなくないRiSKのブログ

危ないRiSKのブログだったかもしれない。本当はRiSKだけどググラビリティとか取得できるIDの都合でsscriskも使ったり。

オーバーロードに優先順位を付ける

動機

enable_switch - 複数の重複しうるコンパイル時条件で、SFINAE によるオーバーロードを書くには - ボレロ村上 - ENiyGmaA Code
これと同じです。重複する条件があるときにSFINAEだけではめんどうかつDRYじゃないことがあります。スマートな解法がほしいです。

問題

以下はコンパイルエラーになります。int を関数 f へ渡すと二つの関数のオーバーロードを解決できないからです。

#include<type_traits>
#include<iostream>

extern void* enabler;

template<class T, typename std::enable_if<std::is_same<T, int>::value>::type*& = enabler>
void f(T&&)
{
 std::cout << "int" << std::endl;
}

template<class T, typename std::enable_if<std::is_integral<T>::value>::type*& = enabler>
void f(T&&)
{
 std::cout << "integral" << std::endl;
}

int main()
{
 f(0); // どっちの f ?
}

このとき,「int を渡したら is_same の方を呼んでほしい」とします。そしたら, is_integral の方に is_same じゃなかったら,というコードを書かなければなりません。なんで二つの関数に is_same を書かなきゃならないのでしょう?ばかばかしいです。これで,条件が増えていったら,同じようなことを繰り返さなければならず,あっという間にカオスになるのは目に見えています。
本質をコードに書きたいです。「こっちの f 優先。そっちの f は次」って書きたいです。

解法

ちっちゃいライブラリ書きました。その名も C++ Overload Priority Library です。大げさ!
次のように使います。

#include<type_traits>
#include<iostream>
#include<sscrisk/overload_priority.hpp>

using namespace sscrisk;

extern void* enabler;

template<class T, typename std::enable_if<std::is_same<T, int>::value>::type*& = enabler>
void f(T&&, overload_priority::highest)
{
 std::cout << "int" << std::endl;
}

template<class T, typename std::enable_if<std::is_integral<T>::value>::type*& = enabler>
void f(T&&, overload_priority::higher)
{
 std::cout << "integral" << std::endl;
}

int main()
{
 f(0, use_overload_priority);
}

実行結果:

int

int は is_same も is_integral も true ですが,優先順位が高い is_same の方が呼ばれました。仮に優先順位だけ入れ替えれば,今度は "integral" が出力されます。すばらしい!

おまけ

当初は次のように書いていました。

typedef bool&& highest;
typedef bool const && higher;
typedef bool const & high;
typedef int&& low;
typedef int const && lower;
typedef int const & lowest;
bool use_overload_priority(){return false;}

しかし,完成してから Boost - Dev - Like boost::enable_if, but takes a "priority number" and changes overload resolution order これを見つけてしまったので書き直しましたw