34

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

Community
  • 1
  • 1
W.F.
  • 13,888
  • 2
  • 34
  • 81
  • 4
    The standard explicitly states that lambdas are an exception. But I assume you're more interested in the rationale when asking why. – krzaq Oct 25 '16 at 14:20
  • 1
    @krzaq exactly - I don't get the reasoning behind preventing programmers from using it... – W.F. Oct 25 '16 at 14:21
  • Are we sure the definition of the unnamed class type is scoped to the scope it is declared in? I always thought they made it global and it is just the instance that is locally scoped. – NathanOliver Oct 25 '16 at 14:24
  • 2
    Then I guess it's just a rule that hasn't been relaxed since C++98 and may be relaxed in the future. But I don't have enough confidence in this guess to put it as an answer. – krzaq Oct 25 '16 at 14:25
  • 1
    @NathanOliver "*A local class of non-closure type shall not have member templates.*". It's referred to as a local class, so it should be local. Also "*The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression*" – krzaq Oct 25 '16 at 14:26
  • @NathanOliver by unnamed class you mean the type of lambda? – W.F. Oct 25 '16 at 14:31
  • 4
    @krzaq is right. In C++17 local classes are allowed to have template members. A note at the bottom of http://en.cppreference.com/w/cpp/language/class_template says "Local classes and any templates used in their members are instantiated as part of the instantiation of the entity within which the local class or enumeration is declared. (since C++17)" – Donghui Zhang Oct 25 '16 at 14:32
  • 1
    @DonghuiZhang is this a recent change? I still see "A local class of non-closure type shall not have member templates." in N4606 – krzaq Oct 25 '16 at 14:35
  • 5
    @DonghuiZhang that's not what that note means, sadly. – ecatmur Oct 25 '16 at 14:39
  • I may have misunderstood the sentence. @ecatmur would you educate me? – Donghui Zhang Oct 25 '16 at 14:44
  • @ecatmur yes - now I see it's more about template class/functions used by local classes... but first read was kind of misleading :) – W.F. Oct 25 '16 at 14:45
  • @DonghuiZhang here's an example: https://godbolt.org/g/IVm2bC - prior to C++17 the order and location of instantiation of `g::X` and `f` was unspecified. – ecatmur Oct 25 '16 at 14:49
  • @krzaq I'm not aware when the change was made. – Donghui Zhang Oct 25 '16 at 14:57
  • @ecatmur from the first part of that note "local classes and any templates used in their members are instantiated...(C++17)", I inferred that in C++17 local classes may have template members. Do you agree or disagree? – Donghui Zhang Oct 25 '16 at 14:57
  • 4
    @DonghuiZhang I would read "templates used in their members" as e.g. "members returning `std::vector`" (where the template being used is `std::vector`). – Angew is no longer proud of SO Oct 25 '16 at 14:59

2 Answers2

28

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.

Engineero
  • 12,340
  • 5
  • 53
  • 75
Columbo
  • 60,038
  • 8
  • 155
  • 203
  • Figured there was a core issue for this but totally failed to find it. Nice catch. – Barry Oct 25 '16 at 15:29
  • 5
    @Barry I knew of it beforehand, since I started writing a paper for it – Columbo Oct 25 '16 at 15:29
  • Thank you for the comprehensive answer. I had a feeling there is no arguments against local classes with templated members and the limitation is just because of previous wording. – W.F. Oct 25 '16 at 15:41
  • 1
    But see https://groups.google.com/a/isocpp.org/d/msg/std-proposals/qd3L1-bGg1A/IhkqByIbCgAJ – T.C. Oct 25 '16 at 20:42
  • @T.C. Aren't the problems he addresses more related to implementability than actual semantics? – Columbo Oct 25 '16 at 20:45
  • Sure, but I heard that the committee tries pretty hard not to standardize something that isn't implementable... – T.C. Oct 25 '16 at 20:47
  • @T.C. I don't understand what Richard is referring to, can you elucidate? AFAICS a generic lambda suffers from the exact same problem (or none, if there is none) as a member template: I can extract the closure type from the function template and force an instantiation of the member function template after the enclosing function template's instantiation. – Columbo Oct 25 '16 at 21:34
  • @Columbo My understanding is that he's referring to the fact that the body of a member function template of a class template isn't instantiated unless used: `template struct C { template void f() { /* stuff */ } }; C c; /* only a declaration of C::f is instantiated */`, and the difficulty in applying a similar model to a member template of a local class of a function template. – T.C. Oct 25 '16 at 21:39
  • @T.C. But isn't that exact issue already applying to generic lambdas? I cannot instantiate the body of a generic lambda right away, because it may depend on the parameters' types. – Columbo Oct 25 '16 at 21:45
  • 2
    @Columbo Not really: `template struct C { template void f() { T t; } }; C c;` is well-formed; `template void f() { [](auto&&) { T t; }; } f();` isn't. – T.C. Oct 25 '16 at 23:03
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/126674/discussion-between-columbo-and-t-c). – Columbo Oct 25 '16 at 23:04
  • @Columbo so the answer isn't that trivial as I expected after all... I need a little time to process all these information. Thanks once again! – W.F. Oct 26 '16 at 05:48
  • You said "... they were identical to local classes with corresponding member template operator(). However, they actually aren't, ...". Why? It seems that your example is supposed to demonstrate this, but it doesn't compare the lambda with a local class that has a template operator() (that is actually not allowed at all!). – Johannes Schaub - litb Jul 01 '17 at 10:36
  • A closer comparison (IMO!) would be to compare against `class X { template void foo() { void t; } };`, because like in the lambda case, which is a non-template class, `X` now is a non-template class. The lambda appears in a function template `bar`, but instantiating the function template instantiates the lambda class. But instantiating `X` will *not* instantiate the function template `foo`, so that's IMO an unfair comparison. – Johannes Schaub - litb Jul 01 '17 at 10:42
  • @JohannesSchaub-litb My example is supposed to show the difference between a template and a generic lambda. I cannot show the difference between a generic lambda and a local class with member templates, since, as you pointed out, the latter is plainly ill-formed. The example you showed is what instantiated generic lambdas correspond to, but that's not related to the point I was trying to make. Because the main problem are local templates within function templates. – Columbo Jul 01 '17 at 10:46
3

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 2
    What if it's not "an important problem that needs to be solved" but "an obvious language inconsistently that clearly ought to be rectified?" – Nicol Bolas Oct 25 '16 at 15:25
  • 2
    @NicolBolas Same conclusion really. Plus you could say that an "obvious language inconsistency" is an important problem. – Barry Oct 25 '16 at 15:26
  • @Barry `If you feel that this is an important problem that needs to be solved` well I believe it might be important problem but as a non-native English speaker I would probably fail on wording :) – W.F. Oct 25 '16 at 16:03