3

Following with the tuple continuation monad, say I define a functor std_tuple to go from the cathegory of the monad-tuple to std::tuple:

auto std_tuple = [](auto... args)
{
    return [=](auto f){ return f(std::make_tuple(args...)); };
};

Now we can use monad-tuples in contexts expecting std::tuple:

template<typename... ARGS>
void f( const std::tuple<ARGS...>& t ){}

int main()
{
    tuple(1,2,3)(std_tuple)(f);
}

So far so good. Except this doesn't compile. Clang 3.4.1 complains:

note: candidate template ignored: couldn't infer template argument '$auto-1-0'

on the f(t) call inside the std_tuple functor.

Is this correct, are not those template argumments deducible? In case afirmative, why?

Community
  • 1
  • 1
Manu343726
  • 13,969
  • 4
  • 40
  • 75
  • The apply operator of the closure type is a function template like `template decltype(auto) operator() (T)`, it cannot deduce a type for just the id-expression `f`. You could wrap that `f` in a lambda, `[](auto const& t){ return f(t); }` – dyp Aug 27 '14 at 18:02
  • 1
    A perfectly forwarding lambda-wrapper to pass an overload set is quite useful for C++14's polymorphic lambdas. Unfortunately, it's quite some boilerplate without macros: `#define LAMBDA_WRAP(FUN) ([](auto&&... args){ return FUN(std::forward(args)...); })` – dyp Aug 27 '14 at 18:10
  • @dyp why the `()` around the lambda? – Yakk - Adam Nevraumont Aug 27 '14 at 19:43
  • @Yakk I didn't want to prove there wouldn't be any funny parsing problems, so I used the common `()` around macro expansions. – dyp Aug 27 '14 at 20:04
  • @dyp Ah: was just wondering if you knew of such a case. Now that I think of it, as lambdas in an unevaluated context are illegal, I'm not sure what harm it can do (`decltype(X)` and `decltype((X))` are distinct, for example, but you cannot put a lambda into the `X` anyhow). – Yakk - Adam Nevraumont Aug 27 '14 at 20:11
  • 3
    @Yakk @dyp two consecutive left square bracket tokens (e.g. a lambda-introducer inside a subscript expression) are parsed as introducing an attribute; see dcl.attr.grammar/6. Also, an empty lambda-introducer following the `delete` keyword must be enclosed in parentheses or it is parsed as `delete[]`; see expr.delete/1. – ecatmur Sep 02 '14 at 12:47
  • 1
    @ecatmur The subscript one might break otherwise working code -- `array[LAMBDA_WRAP(f)]` -- which is a valid use of the macro (passing an overload set object to a subscript operator, say for slicing purposes). The other one, not so much (valid). (you should not be directly invoking `LAMBDA_WRAP(f)`, you should only use it when passing `f` itself as an argument to something) – Yakk - Adam Nevraumont Sep 02 '14 at 12:51

1 Answers1

4

A simple case that reproduces your problem:

void f(int) {}
void f(double) {}

template<class T> void call_with_3( T t ) { t(3); }

int main() {
  call_with_3( f );
}

Here we can see that which f to call cannot be determined at the point where we pass it to call_with_3. Now, you seemingly don't have multiple overloads (you only have one f!), but...

A template is not an instance. A template function is a factory of functions, not a function.

There is no object or value there to pass around.

When you pass a function name as an argument, overload resolution kicks in. If the target type is known (as a function reference or pointer) it is used to do overload resolution on the function name.

In this case, you are passing a function name to a template (auto argument), so there is no overload resolution that can be done, so no particular value can be found, so you get an error.

You can create an object whose effect is to do overload resolution on the invoked arguments with a given function name. I call them overload set objects.

static struct f_overload_set_t {
  template<class... Args>
  auto operator()(Args&&... args) const {
    return f(std::forward<Args>(args)...);
  }
} f_overload_set;

in C++11 you need a ->decltype( f( std::declval<Args>()... ) ) after the const.

Now f_overload_set(blah) will, when invoked will (almost) do what happens when you f(blah), but f_overload_set is an actual object. So you can pass it around.

Macros that generate such overload sets are relatively easy to write. They can also use lambdas, as the above is a lot like a stateless lambda if you think about it.

The nice thing about the stateless lambda based macro overload set generator is that it can be created at point-of-use. From @dyp's comment above:

#define OVERLOAD_SET( FUNC )\
  ([](auto&&... args){\
    return FUNC(std::forward<decltype(args)>(args)...);\
  })

(note: no brackets around FUNC, as that blocks ADL). Brackets around everything else, because otherwise if used within a subscript operation (operator[]), it would be parsed as a [[ starting an attribute, among other spots (thanks to @ecatmur))

which makes your code:

template<typename... ARGS>
void f( const std::tuple<ARGS...>& t ){}

int main() {
  tuple(1,2,3)(std_tuple)(OVERLOAD_SET(f));
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • So the signature of the underlying `operator()` is `template auto operator()( T )`, and I'm passing a template. Of course `f` parameters are not deducible in that case, that makes sense. I just forgot what a generic lambda actually is. Thanks a lot. – Manu343726 Aug 27 '14 at 20:17