Search code examples
c++templatesc++14local-classgeneric-lambda

Why generic lambdas are allowed while nested structs with templated methods aren't?


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).


Solution

  • 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.