c++17 で追加された std::is_invocable
について考えた。
1つ目のテンプレートパラメタが判定対象の型になっている。ある関数について、ある型の引数を渡したときの呼び出しの可否を調べたい場合、確かに関数(と渡す引数)の型さえ分かれば呼び出しの可否は判定できるので、この形には納得がいく。関数の型は decltype
で取ればいい。
#include <iostream>
#include <type_traits>
#include <boost/type_index.hpp>
struct A {};
struct B {};
// no overload
int foo([[maybe_unused]] A const& a, [[maybe_unused]] B const& b) {
return 42;
}
// overload
int ovlFoo(int, [[maybe_unused]] A const& a) {
return 9;
}
float ovlFoo(int, [[maybe_unused]] B const& b) {
return 2.7182;
}
template <class T>
auto ti() {
return boost::typeindex::type_id_with_cvr<T>();
}
template <class T, class U>
void showTypes() {
std::cout << "(" << ti<T>().pretty_name() << ", " << ti<U>().pretty_name() << ")" << std::endl;
}
template <class T, class U>
void test0() {
if constexpr (std::is_invocable_v<decltype(foo), T, U>) {
std::cout << "Can call foo: ";
} else {
std::cout << "Cannot call foo: ";
}
showTypes<T, U>();
}
と思いきや、オーバーロードされている関数について、任意の型で呼び出せるか判定したい場合に、はたと手が止まってしまった。
is_invocable
の1つ目のパラメタに、decltype
で型を取ろうとしても、オーバーロードされた関数の様な複数の型が返ってくるようなものに decltype
を適用するのは不適格とされているので使えない。
私はこの時点で一旦 is_invocable
の使用は諦めて SFINAE で判定すればいいやと思っていた。
しかしある時 c++20 で導入されたコンセプト std::invocable
も同じ様なテンプレート引数を取るようだという事を知ってしまった。
このコンセプトを使う上で、使い方をキチンと分かっておく必要がある気がしたので、ここで漸く真面目に考えたところ、こういう時は関数オブジェクトクラスを使ってたのでは?という単純な事に気がついた。
ラムダが使えるようになってからは出番が激減していたので、その存在が記憶の隅っこに追いやられて居たのか、なかなかコレを思いつかなかった。
最終的に結局はジェネリックラムダを使う方法が良いのかな、という目下の結論に至ったのだが、その過程を記事にする。