1

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.

max66
  • 65,235
  • 10
  • 71
  • 111
Maki
  • 177
  • 7
  • 2
    https://en.cppreference.com/w/cpp/types/is_invocable – n314159 Dec 10 '19 at 20:41
  • 1
    "is_detected idiom didn't complain when I tried passing a float to a function expecting an int" you can pass a float to a function expecting an int. Try it; `foo(3.14f)`. If foo's signature was `foo( exactly x )`, where `exactly` blocked all conversions, your detected code would work. But, as written, you got the answer to the question you asked... – Yakk - Adam Nevraumont Dec 10 '19 at 21:27
  • 1
    `foobar(bar, 1, 2.4, 3)` -- `bar` is not an object (or value), it is the name of an overload set. You cannot pass overload sets as function parameters. There are workarounds, but they don't involve directly passing `bar`. – Yakk - Adam Nevraumont Dec 10 '19 at 21:28
  • Ok, maybe I wasn't clear enough about my intentions. I'm trying to determine if the function accepts the exact types passed to it without casting – Maki Dec 10 '19 at 22:18
  • Also, I didn't intend to use it this way: ```foobar(bar, 1, 2.4, 3)```, I would expect more something like this: ```foobar_v```. That was more of an example with which I wanted to show my intent – Maki Dec 10 '19 at 22:22

1 Answers1

1

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 );
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • Looking back at my code, I see that I made a mistake. I don't want to statically assert foobar. Let me change it quickly, and come back to this answer – Maki Dec 10 '19 at 22:28
  • @Maki - yes, I see... but I don't see a great difference. Anyway... I don't think that `func_with_signature_exists_v` it's possible / make sense. Maybe `func_with_signature_exists_v` that is, almost, what I've made in my answer. – max66 Dec 10 '19 at 22:50
  • Alright, I've removed the static assert. I hope that my intentions are clearer now. Looking at your working example, in the static_assert, what if I don't know the number of parameters and their types? What if I have a variadic template? Would this make any sense? ```template void fun(Ts&& args) { static_assert(foobar()...>(foo) == false); }``` – Maki Dec 10 '19 at 22:52
  • 1
    @Maki - no, doesn't make sense for a template function; variadic or not. You **can't** pass a template function as function argument. – max66 Dec 10 '19 at 23:08