Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda as default argument fails

Tags:

c++

c++17

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?

like image 436
template boy Avatar asked May 14 '15 14:05

template boy


People also ask

When lambda function has only one parameter what is default name?

Lambda expressions Like anonymous functions, lambda expressions allow no default parameters and cannot be called with named arguments. Since they are stored immediately as a function type like (Int, Int) -> Int , they undergo the same restrictions as function types referring to actual functions.

When default arguments evaluated in python?

Python's default arguments are evaluated only once when the function is defined, not each time the function is called. This means that if a mutable default argument is used and is mutated, it is mutated for all future calls to the function as well.

Do not use mutable objects as default arguments in python?

Do not use mutable default arguments in Python, unless you have a REALLY good reason to do so. Why? Because it can lead to all sorts of nasty and horrible bugs, give you headaches and waste everyone's time. Instead, default to None and assign the mutable value inside the function.


3 Answers

Type deduction for auto does not consider default arguments.

like image 82
Daniel Frey Avatar answered Oct 19 '22 19:10

Daniel Frey


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.

like image 20
Denilson Amorim Avatar answered Oct 19 '22 20:10

Denilson Amorim


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.

like image 2
Yakk - Adam Nevraumont Avatar answered Oct 19 '22 18:10

Yakk - Adam Nevraumont