19

I get an error with the latest versions of clang and gcc with this code:

int main() {
    auto lambda = [] (auto = [] {}) {};
    lambda();
}

Clang gives the error:

prog.cc: In function 'int main()':
prog.cc:3:12: error: no match for call to '(main()::<lambda(auto:1)>) ()'
     lambda();
            ^
prog.cc:2:35: note: candidate: template<class auto:1> main()::<lambda(auto:1)>
     auto lambda = [] (auto = [] {}) {};
                                   ^
prog.cc:2:35: note:   template argument deduction/substitution failed:
prog.cc:3:12: note:   couldn't deduce template parameter 'auto:1'
     lambda();
            ^

Why does this fail?

3
  • You'll need C++14 for generic lambdas. Does that still fail? Commented May 14, 2015 at 14:41
  • @MikeSeymour Yes, I'm even compiling with -std=c++1z. Commented May 14, 2015 at 14:42
  • 8
    lambda expression is irrelevant. you'd get the same error with auto = 1 Commented May 14, 2015 at 14:44

3 Answers 3

22

Type deduction for auto does not consider default arguments.

Sign up to request clarification or add additional context in comments.

3 Comments

This is so also for ordinary templates. I find it often helps to think in terms of code transformation. E.g. also for understanding definitions of member functions within a class definition.
If you need some example code of what fails, all 3 commented out lines here fail to compile, without the complexity of passing a lambda to a lambda.
Type deduction for auto is the same for templates (minus the initializer_list thing). You can't deduce the type of a template argument through its default argument.
13

Since lambdas are sugar for functors, the issue is on the fact template functions are unable to deduce template arguments (auto) in this default context.

A lambda can be reduced to the functor struct level by taking in consideration those statements:

§5.1.2/3 [expr.prim.lambda]

The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type [...]

§5.1.2/5 [expr.prim.lambda]

[...] For a generic lambda, the closure type has a public inline function call operator member template (14.5.2) whose template-parameter-list consists of one invented type template-parameter for each occurrence of auto in the lambda’s parameter-declaration-clause, in order of appearance. [...]

As such the type of your lambda is equivalent to this functor type:

struct unnamed
{
    template<typename Auto1>
    auto operator()(Auto1 = []{})
    {
    }
};

And your usage is then equivalent to:

int main() {
    auto lambda = unnamed();
    lambda();
}

The type of Auto1 is unable to be inferred in this context as specified in §14.8.2.5/5 [temp.deduct.type]:

The non-deduced contexts are:

[...]

— A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.

Comments

2

Template functions (or methods) do not deduce their type parameters from their default arguments, and a closure with auto parameters is merely an object with a template method.

This makes having a default lambda for a template function a bit annoying.

One approach would be to type erase calling an object, without storing it, like so:

#include <utility>
#include <type_traits>
#include <memory>

template<class Sig>
struct function_view;

template<class R, class...Args>
struct function_view<R(Args...)>{
  void* state;
  R(*f)(void*, Args&&...);

  template<class F, class=std::enable_if_t<std::is_convertible<std::result_of_t<F&(Args...)>,R>{}>>
  function_view( F&& fin ):
    state(const_cast<void*>(static_cast<void*>(std::addressof(fin)))),
    f( [](void* state, Args&&...args)->R{
      F&& f = std::forward<F>(*static_cast<std::decay_t<F>*>(state));
      return f(std::forward<Args>(args)...);
    })
  {}
  function_view( R(*fin)(Args...) ):
    state(fin),
    f( fin?+[](void* state, Args&&...args)->R{
      R(*f)(Args...) = static_cast<R(*)(Args...)>(state);
      return f(std::forward<Args>(args)...);
    }:nullptr)
  {}
  explicit operator bool(){return f;}
  function_view():state(nullptr),f(nullptr){}
  function_view(std::nullptr_t):function_view(){}
  R operator()(Args...args)const{
    return f(state, std::forward<Args>(args)...);
  }
};
template<class...Args>
struct function_view<void(Args...)>{
  void* state;
  void(*f)(void*, Args&&...);

  template<class F, class=std::result_of_t<F&(Args...)>>
  function_view( F&& fin ):
    state(const_cast<void*>(static_cast<void*>(std::addressof(fin)))),
    f( [](void* state, Args&&...args){
      F&& f = std::forward<F>(*static_cast<std::decay_t<F>*>(state));
      f(std::forward<Args>(args)...);
    })
  {}
  function_view( void(*fin)(Args...) ):
    state(fin),
    f( fin?+[](void* state, Args&&...args){
      void(*f)(Args...) = static_cast<void(*)(Args...)>(state);
      f(std::forward<Args>(args)...);
    }:nullptr)
  {}

  explicit operator bool(){return f;}
  function_view():state(nullptr),f(nullptr){}
  function_view(std::nullptr_t):function_view(){}
  void operator()(Args...args)const{
    f(state, std::forward<Args>(args)...);
  }
};

int main() {
  auto f = [] (function_view<void()> x=[]{}) {
    x();
  };
  f();
}

As this just works with function pointers, and I have had good experience with gcc inlining simple function pointers, it might not have as high a performance impact as std::function. And unlike std::function no virtual tables or heap allocation is involved.

live example

For a non-lambda, you can do this:

template<class X=function_view<void()>>
void f( X&& x=[]{} ) {
  x();
}

which deduces if you pass is an argument, and becomes a function-at-nothing if you don't. You could also do:

struct do_nothing {
  template<class...Args>
  void operator()(Args&&...)const{}
};

template<class X=do_nothing>
void f( X&& x=do_nothing{} ) {
  x();
}

which might be easier to optimize.

3 Comments

haha 5 years ago I thought I knew C++...... i can't even comprehend the easy snippets lol
@imso 5 years ago, the above wasn't C++
i know, crazy how some languages progress vs others - or is it just a bias of questions about the newer features? that's probly it..

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.