10

Consider this simple (bad) function template for which lots of variations exist on this site:

template <typename R, typename... Args>
R call_with(std::function<R(Args...)> f,
            Args... args)
{
    return f(args...);
}

And two attempts at calling it:

call_with([]{});        // (a)
call_with<void>([]{});  // (b)

I cannot call (a) because a lambda is not a std::function<R(Args...)> so template deduction fails. Straightforward.

However, (b) also fails. I suspect this is because the compiler cannot determine that I mean to provide all the type arguments and reasons that I am simply providing R - so it is trying (and failing) to deduce Args... for the same reason that the initial call failed.

Is there a way to explicitly specify that I am providing all the template arguments? To clarify, I am interested only in how to explicitly provide the template arguments so that there is no template deduction - I am not looking for the correct way to write call_with or for a way to make template deduction succeed when called with a lambda.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I see a failure even if I supply arguments: http://coliru.stacked-crooked.com/a/7d2e89a69f8d738d – Jay Miller Jul 15 '15 at 21:16
  • 1
    @JayMiller Right, same idea. Although if we rewrote `call_with` to always take a single argument (`template R call_with(std::function, A );`) then explicitly providing template arguments works fine (`call_with([](int ){}, 42);`). It's just the parameter pack case that's stubborn. – Barry Jul 15 '15 at 21:25
  • 2
    I should not have deleted my earlier comment, which allowed @Barry to clarify that this is not a question about *how to get around this issue* and the question being asked is exactly as written. *"Is there a way to explicitly specify..."* – Drew Dormann Jul 15 '15 at 22:17

2 Answers2

5

The short answer to your--edited--question is: if you cannot change the declaration of call_with(), then either use the type casts demonstrated by @CoffeeandCode, or use the technique described below to create a wrapper for call_with().

The problem is in the fact that the compiler is trying to deduce the template arguments from the first function argument. You can prevent this if you write your code like this:

#include <functional>
#include <iostream>

// identity is a useful meta-function to have around.
// There is no std::identity, alas.
template< typename T>
struct identity
{
    using type = T;
};

template <typename R, typename... Args>
R call_with( typename identity<std::function<R(Args...)>>::type f,
            Args... args)
{
    return f(args...);
}

int main()
{
    call_with<void>([](int){ std::cout << "called" << std::endl; }, 2);
}

Using a template meta-function to "generate" the std::function type means that the compiler cannot even try to deduce the function type from the first argument and it will only use the other arguments.

You would still need to explicitly provide the return type of course, but for the other arguments you can now choose whether to explicitly specify them or leave it up to the compiler to deduce them from the arguments given.

If you really want to enforce that all template arguments are provided and not deduced, you can also wrap the argument pack in an invocation of identity this way:

template <typename R, typename... Args>
R call_with( typename identity<std::function<R(Args...)>>::type f,
            typename identity<Args>::type... args)

In summary, if you want to prevent the compiler from deducing function template argument types that also appear as function parameters, wrap them in a meta function such as identity.

dhavenith
  • 2,028
  • 13
  • 14
  • 1
    Clever, and definitely lets me avoid deduction (+1). Although requires mucking with the signature, which is unfortunate. – Barry Jul 16 '15 at 15:24
  • Yes, I'm afraid that with the given signature, the compiler will always try to deduce template arguments--and fail unless the first argument is of type std::function< some args..>. – dhavenith Jul 16 '15 at 22:35
1

You could specify the function type beforehand, like this:

int main(){
    std::function<void()> f = []{};
    call_with(f);
}

or, in a little more messy but more compact way:

int main(){
    call_with(static_cast<std::function<void()>>([]{}));
}

This is because the compiler doesn't know what return type and arguments to deduce for your template parameters before you ask it to implicitly convert a lambda, which is an unspecified function object defined by the compiler, to a std::function with those template parameters.

Really, you should just change your function warpper to be more generic:

template<typename Functor, typename ... Args>
auto wrapper(Functor &&f, Args &&... args) -> decltype(f(std::forward<Args>(args)...)){
    return f(std::forward<Args>(args)...);
}

This should work for any function or functor type. It's also a really good example of the use of trailing return types.

Here's a live example

RamblingMad
  • 5,332
  • 2
  • 24
  • 48
  • While all true, this does not at all answer the question. The question is strictly about explicitly providing all types to `call_with`, not about how to write that function correctly. – Barry Jul 16 '15 at 10:38
  • @Barry I gave a way to specify your call to `call_with` in the first part of my answer. Using a `static_cast` is just telling the compiler what to put for those template arguments. – RamblingMad Jul 16 '15 at 10:40
  • No it's not. It's changing the argument types so that template deduction can succeed. I am looking for a solution that does not involve template deduction *at all*. – Barry Jul 16 '15 at 10:41
  • How is it changing a `std::function` from a `std::function`? You explicitly write `std::function` in your signature, this is simply explicitly casting the lambda. – RamblingMad Jul 16 '15 at 10:42
  • Of course you can. `template void foo(T ) { }; foo(42);` calls `foo` and doesn't do template deduction (which would have instantiated `foo`) – Barry Jul 16 '15 at 10:44
  • @Barry you're absolutely right, that was just me being too quick on the trigger haha. – RamblingMad Jul 16 '15 at 10:45