C++11 and the lack of polymorphic lambdas - why?

2019-01-16 12:39发布

问题:

I've been reviewing the draft version of the C++11 standard. Specifically the section on lambdas, and I am confused as to the reasoning for not introducing polymorphic lambdas.

For example, amongst the 100001 ways polymorphic lambdas could be used, I had hoped we could use code such as the following:

template<typename Container>
void foo(Container c)
{
    for_each(c.begin(), c.end(), [](T& t) { ++t; });
}

What were the reasons:

  • Was it that the committee ran out of time?

  • That polymorphic lambdas are too hard to implement?

  • Or perhaps that they are seen as not being needed by the PTB?

Note: Please remember the example above is not the only one, and it is only provided as a guide to the types of code. Answers that solely concentrate on providing a workaround for the above piece of code will not be considered as valid!

Related sources:

  • Lambda expressions and closures for C++ (document number N1968=06-0038)
  • Can lambda functions be templated?

回答1:

The reason we don't have polymorphic lambdas is explained pretty well in this posting.

It has to do with the concepts feature that was pulled from C++11: essentially, polymorphic lambdas are ordinary, unconstrained function templates and we didn't know how to typecheck a concept-constrained template that used an unconstrained template. However, solving that problem turns out to be easy as shown here(dead link), so I don't think there's any obstacle remaining.

The link to cpp-next is dead; the relevant info can be found here



回答2:

Since the argument, c, meets the STL requirements for a container, you should be able to use something like

template<typename Container>
void foo(Container c)
{
    for_each(c.begin(), c.end(),[](typename Container::reference t) { ++t; });
}

I'll also showcase John Purdy's comment above, which is another way to get the typename you want in this lambda:

template<typename Container>
void foo(Container c)
{
   for_each(c.begin(),c.end(),[](decltype(*c.begin()) t) { ++t; });
}

(Yes, Dominar, I know you don't like this answer, because it doesn't answer your question, but I'm willing to bet that the next person who comes along asking this question is going to be looking for a way to make their code work, so it does make sense to have some techniques around where the question is relevant.)



回答3:

It's probably because there already is a syntax for doing that, and the purpose of lambdas is to introduce a much simpler syntax that covers most cases. When you try to cover all cases (what if you wanted the auto-generated functor to inherit a particular base class?), you lose the comparative advantages (simplicity and terseness) of the lambda.

I really don't like the proposed syntax. Is T a keyword? Do all identifiers for which name lookup fails get turned automatically into template typename arguments? That prevents you from detecting misspellings, which IMO is a BAD idea:

for_each(c.begin(),c.end(),[](iterater& t) { ++t; });
// programmer misspelled "iterator" and now has a polymorphic lambda, oops

It also introduces action-at-a-distance behavior, if the named type get introduced in some header file somewhere, the meaning changes suddenly. Also really BAD.

Well, since it's supposed to create a template, we could borrow the existing syntax:

for_each(c.begin(),c.end(),[]template<typename T>(T& t) { ++t; });

This is unambiguous and now allows non-type template arguments (useful for accepting arrays by reference), but is really unwieldy. At this point you're better off writing out the functor by hand, it'll be much easier to understand.

However, I think a simple syntax is possible using the auto keyword:

for_each(c.begin(),c.end(),[](auto& t) { ++t; });

This next section incorrectly assumes that the template parameter appears on the functor type rather than its operator()():

But now you have a problem that for_each infers a typename template argument, not a template template argument. Type inference isn't possible in that context.

In the current proposal, lambdas have type, even if it's an unmentionable (other than decltype) type. You'd have to lose that feature in order to accommodate inference at the call-site.

Example showing that the issue is NOT a shortcoming of lambdas, it's simply a non-deducible context:

#include <vector>
#include <algorithm>
#include <iterator>

int main(void)
{
    using namespace std;
    vector<int> a(10);
    vector<int> b(10);
    vector<int> results;

    transform(a.begin(), a.end(), b.begin(), back_inserter(results), min<int>);
}

The template type parameter to std::min must be explicitly specified. Lambdas are no different from using existing functors in this regard.

EDIT: Ok, now that I realize we aren't suggesting that the lambda generate a template functor type, but a single non-template functor type which implements a templated function application operator (operator()()), I agree that the compiler should be able to generate such a thing. I propose that using the auto keyword here would be a good simple syntax for requesting that.

However, I'm not really happy with auto either. What about lambdas with multiple parameters:

[](auto& x, auto& y){ return x + y; }
//becomes
template<typename T1, typename T2>
auto operator()(T1& x, T2& y) -> decltype(x + y) { return x + y; }

Ok, that works well enough, but what if we wanted two parameters but only one type argument:

[](auto& x, decltype(x)& y){ return x + y; }
//becomes
template<typename T1>
auto operator()(T1& x, T1& y) -> decltype(x + y) { return x + y; }

Seems ok, but I find the syntax misleading. The syntax suggests that the type parameter is inferred from the first actual parameter, and the second parameter is coerced to the same type, but actually both actual parameters are considered equal during type inference.

Perhaps it's best that this case be limited to one lambda parameter per type argument, and if you want something more constrained, write the functor yourself. This seems to me to be a good compromise between flexibility and power vs keeping the syntax simple.



回答4:

Well, now that you've linked n1968, the answer to your question is apparent. It's found in section 5.1 of the proposal.



回答5:

The following (your comment to my other answer above) works:

#include <algorithm>
#include <vector>

struct foo
{
   template<typename T>
   void operator()(T& t)
   {
      ++t;
   }
};

int main()
{

   std::vector<int> v;
   std::for_each(v.begin (),v.end(),foo());

   return 0;
}

But the following does not:

#include <algorithm>
#include <vector>

template<typename T>
struct foo
{
   void operator()(T& t)
   {
      ++t;
   }
};

int main()
{

   std::vector<int> v;
   std::for_each(v.begin (),v.end(),foo()); // <-- the syntax for foo here 
                                            //     is kinda fictitious

   return 0;
}

Probably the C++ committee saw lambdas as being more similar to the second example than the first. (Though I haven't figured out clever way to define a lambda in which this would make a difference. Anyone got any crazy ideas?)