I'm trying to use template metaprogramming to find a function (not class method) with a specific signature. And to achieve that, I want to use the detection idiom.
Let's say that I have functions foo()
and bar()
:
void foo(int x);
template<typename... Ts>
void bar(Ts&& ...args);
And another function called foobar()
that receives a function and a parameter pack. foobar()
checks if the received function can be called with the parameters received through the parameter pack:
template<typename F, typename... Ts>
void foobar(F&& fun, Ts&& ...args) {
if constexpr(func_with_signature_exists_v<fun(declval<Args>()...)>)
do_something;
}
I would expect the following results from the if
statement in foobar()
:
if constexpr(func_with_signature_exists_v<foo(int)>) // true
if constexpr(func_with_signature_exists_v<foo(float)>) // false
if constexpr(func_with_signature_exists_v<foo(int, int)>) // false
if constexpr(func_with_signature_exists_v<bar(int)>) // true
if constexpr(func_with_signature_exists_v<bar(int, int)>) // true
if constexpr(func_with_signature_exists_v<bar(int, float, int)>) // true
I tried to follow the steps from the accepted answer from this link, but the is_detected idiom didn't complain when I tried passing a float
to a function expecting an int
.
Also, I'd prefer if I could pass any function to the using statement, instead of explicitly specializing it for one specific function, like here:
template<typename... Args>
using test_t = decltype(f(std::declval<Args>()...));
Is there any way to achieve what I'm looking for? Or anything similar to it?
Note that this is for a side project, so I'm trying to use C++17, not C++14 or C++11.
foo()
and bar()
give completely different problems.
First of all: bar()
, that is a template function.
Given that you want that bar()
is a template function (different if is a template operator()
of a class
/struct
), if you want something as
static_assert(foobar(bar, 1));
the best i can imagine, is that foobar()
should bee a C-style macro; and a variadic one, to make the things more complicated (but don't ask to me to develop that macro).
This is because you can't pass a template function as function argument because a template function isn't an object but a set of objects.
For foo()
, if I understand correctly, the problem is that the detection idiom say true when an argument is convertible to the function argument
static_assert(foobar(foo, 3.4)); // you get true but you want false
and that you want to pass the function as an argument.
This is simple to solve (but I don't think is very useful).
If you declare (no definition needed) a couple of overloaded functions as follows
template <typename ... Args, typename R>
std::true_type fwse (R(*)(Args...), int);
template <typename ..., typename T>
std::false_type fwse (T, long);
and a constexpr
variadic template variable
template <typename T, typename ... Args>
constexpr auto func_with_signature_exists_v
= decltype(fwse<Args...>(std::declval<T>(), 0))::value;
you can also write foobar()
as follows
template <typename F, typename ... Ts>
constexpr auto foobar (F, Ts const & ...)
{
if constexpr ( func_with_signature_exists_v<F, Ts...> )
return std::true_type{};
else
return std::false_type{};
}
and you get
static_assert( foobar(foo, 7) == true );
static_assert( foobar(foo, 3.4) == false );
static_assert( foobar(foo, 1, 2) == false );
I don't think is very useful because this works when the types for the foo()
arguments has to be plain types (no const
, no references).
If you can avoid to pass values to foobar()
, and if is OK to pass the Args...
types directly
static_assert( foobar<int>(foo) == true );
static_assert( foobar<double>(foo) == false );
static_assert( foobar<int, int>(foo) == false );
you can rewrite foobar()
as follows
template <typename ... Ts, typename F>
constexpr auto foobar (F)
{
if constexpr ( func_with_signature_exists_v<F, Ts...> )
return std::true_type{};
else
return std::false_type{};
}
and become more flexible (because can accept also const
and/or reference types for Ts...
).
The following is a full compiling example
#include <type_traits>
void foo(int x)
{ }
template <typename ... Args, typename R>
std::true_type fwse (R(*)(Args...), int);
template <typename ..., typename T>
std::false_type fwse (T, long);
template <typename T, typename ... Args>
constexpr auto func_with_signature_exists_v
= decltype(fwse<Args...>(std::declval<T>(), 0))::value;
template <typename ... Ts, typename F>
constexpr auto foobar (F)
{
if constexpr ( func_with_signature_exists_v<F, Ts...> )
return std::true_type{};
else
return std::false_type{};
}
int main ()
{
static_assert( foobar<int>(foo) == true );
static_assert( foobar<double>(foo) == false );
static_assert( foobar<int, int>(foo) == false );
}