Why generic lambdas are allowed while nested struc

2019-02-21 07:18发布

问题:

As far as I understand - generic lambdas are transformed into objects of local scope structs with templated operator(). This makes generic lambda very powerful and easy to use tool. On the other hand one can create structs nested into the function, when however the struct has templated member e.g.:

#include <iostream>

int main() {
    struct inner {
    template <class T>
       void operator()(T &&i) { }
    };
    return 0;
}

or is templated by itself:

int main() {
    template <class T>
    struct inner {
       void operator()(T &&i) { }
    };
    return 0;
}

compiler seems to have a problem with compiling it:

error: invalid declaration of member template in local class

and

error: a template declaration cannot appear at block scope

I assume the problem lays more in c++ standard than in compiler bug. What are the reasons lambdas are allowed to have templated members and not the local structures?

I found this qustion, but I think the answer is kind of outdated (I don't think it's true even for c++11).

回答1:

This is core issue 728, which was filed before generic lambdas were a thing.

You mentioned generic lambdas and that they were identical to local classes with corresponding member template operator(). However, they actually aren't, and the differences are related to implementation characteristics. Consider

template <typename T>
class X {
    template <typename>
    void foo() {
        T t;
    }
};

And

template <typename T>
auto bar() {
    return [] (auto) {T t;};
};

Instantiating these templates with <void> will be fine in the first case, but ill-formed in the second. Why fine in the first case? foo need not be instantiatable for each particular T, but just one of them (this would be [temp.res]/(8.1)).

Why ill-formed in the second case? The generic lambda's body is instantiated - partially - using the provided template arguments. And the reason for this partial instantiation is the fact that…

…the lexical scopes used while processing a function definition are fundamentally transient, which means that delaying instantiation of some portion of a function template definition is hard to support.

(Richard Smith) We must instantiate enough of the local "template" to make it independent of the local context (which includes template parameters of the enclosing function template).

This is also related to the rationale for [expr.prim.lambda]/13, which mandates that an entity is implicitly captured by a lambda if it…

names the entity in a potentially-evaluated expression ([basic.def.odr]) where the enclosing full-expression depends on a generic lambda parameter declared within the reaching scope of the lambda-expression.

That is, if I have a lambda like [=] (auto x) {return (typename decltype(x)::type)a;}, where a is some block-scope variable from an enclosing function, regardless of whether x's member typedef is for void or not, the cast will cause a capture of a, because we must decide on this without waiting for an invocation of the lambda. For a discussion of this problem, see the original proposal on generic lambdas.

The bottom line is that completely postponing instantiation of a member template is not compatible with the model used by (at least one) major implementation(s), and since those are the expected semantics, the feature was not introduced.


Was that the original motivation for this constraint? It was introduced sometime between January and May 1994, with no paper covering it, so we can only get a rough idea of the prevailing notions from this paper's justification of why local classes shall not be template arguments:

Class templates and the classes generated from the template are global scope entities and cannot refer to local scope entities.

Perhaps back then, one wanted to KISS.



回答2:

I assume the problem lays more in c++ standard

Correct. This is stipulated in [temp] for class templates:

A template-declaration can appear only as a namespace scope or class scope declaration.

and [temp.mem] for member templates:

A local class of non-closure type shall not have member templates.


What are the reasons lambdas are allowed to have templated members and not the local structures?

Because once we had lambdas in C++11, it was deemed that it would be extremely useful to extend that concept to have generic lambdas. There was a proposal for such a language extension, which was revised and revised and adopted.

On the other hand, there has not yet been a proposal presented (as far as I'm aware from a brief search) that lays out the motivation for the need for member templates in local classes that isn't adequately solved by a generic lambda.

If you feel that this is an important problem that needs to be solved, feel free to submit a proposal after laying out a thoughtful motivation for why local member templates are important.