What if the lambda expression of C++11 supports default arguments?

13,123

Solution 1

There is no real reason that lambdas can't have default arguments. However, there are two main ways to use lambdas, and only one of them would allow default arguments without changing the type system.

  1. You can call the lambda directly, or through a template. Default parameters would work fine in this case.

  2. You can call the lambda through std::function. Default parameters would not work without changing the type system.

My guess is that new functions that people write in C++11 will usually take std::function parameters because this way, the function won't have to be a template, or it won't have to be instantiated for every single lambda and functor that gets passed to it.

Why can't a std::function (or function pointer) take defaults?

It's not obvious what the type of such a function would be.

  • If it's std::function<int(bool)>, then how do you call it with the defaults? (You can't.)

  • If it's std::function<int(bool=false)>, then what types is it compatible with, and how does conversion work? Can you convert it to std::function<int()>? What about std::function<int(bool=true)>?

  • If it's something new like std::function<int(bool=default)>, then what types is it compatible with, and how does conversion work?

Basically, this isn't just a switch you can flip in the standard and make function pointers / std::function handle default arguments. Default arguments in normal functions are handled using information from the function's declaration, which is not available at the call site for a lambda or function pointer. So you would have to encode information about the defaults into the function type, and then work out all of the non-obvious rules for conversion and compatibility.

So you would have to come up with a compelling case for why such a feature would be added, and convince the committee.

So, why can't lambdas take defaults?

I haven't answered this question. But I don't think it would be a very useful feature to add. I would delete this answer if I could, but it's been accepted. I would downvote it if I could, but it's mine. C'est la vie.

Solution 2

I agree that there is no real "technical" restriction per se to allowing default arguments in lambdas to work in some cases. It wouldn't mangle your pointers and auto, because the type of a function is not affected by default arguments. But that's also why this wouldn't be terribly practical.

Why?

Because default arguments, while part of a function signature, are not part of a function type:

[C++11: 1.3.17]:
signature
<function> name, parameter type list (8.3.5), and enclosing namespace (if any)
[ Note: Signatures are used as a basis for name mangling and linking. —end note ]

[C++11: 8.3.5/6]: [..] The return type, the parameter-type-list, the ref-qualifier, and the cv-qualifier-seq, but not the default arguments (8.3.6) or the exception specification (15.4), are part of the function type. [ Note: Function types are checked during the assignments and initializations of pointers to functions, references to functions, and pointers to member functions. —end note ]

They are essentially a piece of syntactic sugar that are "activated" by the compiler being able to see the declaration of the function you use, and injected at the point of the function call:

#include <iostream>

void foo(int x = 5)
{
   std::cout << x << '\n';
}

int main()
{
   foo();
}
  • Output: 5

The default argument is "visible".

However, when you "hide" your function behind a pointer:

int main()
{
    void (*bar)(int) = &foo;
    bar();
}
  • Error: too few arguments to function

The type of bar is correct, and the compiler knows that foo has a default, but there's simply no direct syntax that exists to inform the compiler at the point of calling bar that bar is also foo. Sure, in this trivial scenario it could figure it out by observing the assignment, but that's hardly justification for the wider argument.

For the same reason, default arguments stated only in a definition not visible at the call site are next to useless:

// a.h
void foo(int);

// a.cpp
#include "a.h"
#include <iostream>

void foo(int x = 5)
{
   std::cout << x << '\n';
}

// main.cpp
#include "a.h"

int main()
{
    foo();
}
  • Error in main.cpp: too few arguments to function

I imagine that this is the reason for:

[C++11: 8.3.6/4]: [..] Declarations in different scopes have completely distinct sets of default arguments. [..]

We are allowed to "pile up" default arguments for class non-template member function definitions ([C++11 8.3.6/6]); the example indicates that this default will still only apply in the same TU, which follows the behaviour we've seen above in my second code snippet.

So, if default arguments are not part of the function type, and must be unambiguously visible to the call site, then there are only a handful of trivially contrived corner-cases in which they would be useful for lambdas, which is when they are called in the same scope that they are created so the compiler can tractibly figure out how to "fill in" the default argument for the call to the lambda and, well, what is the point of that? Not a lot, I tell thee.

Solution 3

As the comments to the question suggest, there's probably no technical reason for there to be no default arguments. But the flip question is "Is there a practical reason for default arguments?" I would think the answer to this is "no", and here's why.

In order to call a lambda, one thing you can do is call it right away

[] { printf("foo"); }();

I'm sure this has limited uses, if any, so let's move on. The only other way to call a lambda is to bind it to a variable first, and there's a few options here.

  1. Use auto. So we get auto foo = [] { printf("foo"); }; foo();
  2. Bind it to a function pointer: void (*foo)() = [] { printf("foo"); }; foo(). Of course, this only works if the lambda doesn't capture.
  3. Do the equivalent of either 1 or 2 by passing the lambda to a function or function template.

Now let's go through the usefulness of default arguments in each case

  1. We're calling the lambda directly in this case, so the code is probably tight-knit enough that we won't be calling the lambda with various numbers of arguments. If we are, the lambda could (should?) probably be refactored into a more general component. I don't see any practical benefit here.
  2. (see 1)
  3. We pass the lambda off to another function. I don't see the practical benefit to a default argument here, either. Think back to good old functors (which can have default arguments) - I can't say I know of too many functions which count on default arguments being present even for these. Since lambdas are effectively just functors, there's no reason for this observation to change suddenly.

I think these points are enough to say the default arguments to a lambda really aren't that useful. Also, I see some people are talking about issues with the type of a lambda if it had default arguments, but this is a non-issue IMO. You can always write up your own functor that does the same thing, and which does have the default argument. Also, about degrading to a function pointer, there's not much to say either. Take a normal function

void func(int i = 0)
{
}

and take it's address. What do you get? A void (*)(int). There's no reason a lambda would follow different rules.

Share:
13,123
xmllmx
Author by

xmllmx

Updated on June 02, 2022

Comments

  • xmllmx
    xmllmx about 2 years

    I think the following code is very handy and no harmful:

    auto fn = [](bool b = false) -> int // NOT legal in C++11
    {
        return b ? 1 : 0;
    }; 
    

    Why does C++11 explicitly prohibit default arguments of the lambda expression?

    I just wonder the rationales and considerations behind.

    I want to know "WHY" rather than "WHAT" the C++11 standard says.