6

The question Lambda expressions as class template parameters asks about the possibility of using lambda expressions as class template parameters.

The answer to the question was no. However, it was about C++11.

Has the situation changed in the new standard, C++14?

Community
  • 1
  • 1
DLunin
  • 1,050
  • 10
  • 20
  • 4
    The rules were tightened in C++14 and considering the [rationale for excluding them form unevaluated operands](http://stackoverflow.com/a/22232165/1708801) I don't think this will change. – Shafik Yaghmour Oct 02 '14 at 19:31
  • Given the last draft, the text cited (with emphasis) in the linked post has not changed. So I would think, no, the situation had not changed. – Niall Oct 02 '14 at 19:35
  • 1
    Er... The answer there was yes, just not directly. Rendering my answer, which was also "yes, just not directly" for C++14, somewhat pointless. –  Oct 02 '14 at 19:39

2 Answers2

2

No the situation in C++14 has not changed at all and in fact the language in section 5.1.2 Lambda expressions paragraph 2 has been tightened from:

A lambda-expression shall not appear in an unevaluated operand (Clause 5).

to:

[...]A lambda-expression shall not appear in an unevaluated operand (Clause 5), in a templateargument, in an alias-declaration, in a typedef declaration, or in the declaration of a function or function template outside its function body and default arguments. [ Note: The intention is to prevent lambdas from appearing in a signature. —end note ][...]

Defect report 1607. Lambdas in template parameters lead to this change.

The defect report only obliquely deals with the rationale for disallowing this but we can find a very detailed explanation for why this is disallowed in Rationale for lambda-expressions not being allowed in unevaluated contexts. The reasons boil down to:

  • Lambda expressions not having a unique type
  • Compiler implementation issues:
    • Such as an extraordinary expansion of SFINAE
    • The possible requirement to name mangle the whole body of a lambda.

Given the rationale for this restriction it seems unlikely to change.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
1

Has the situation changed in the new standard, C++14?

A Lambda expression shall still not appear in an unevaluated operand - the same quote as the one in Xeo's Post also exists in the latest publicly available draft N3797, in the exact same place.

However, every closure type has a deleted default constructor (N3797, §5.1.2/20):

The closure type associated with a lambda-expression has a deleted (8.4.3) default constructor and a deleted copy assignment operator.

So, for portabiliby and standard conformance (and probably for the code to work on reasonable compilers), you would need to pass a closure object to the constructor of the instantiated class to copy from. But to pass a closure object of the same type as the template argument of that specialization you have to define it first anyway:

using my_map_type = map<int, int, decltype([] (auto&& lhs, auto&& rhs) {return lhs < rhs*4;})>;
// Assuming the above compiles

my_map_type m( [] (auto&& lhs, auto&& rhs) {return lhs < rhs*4;} );
// Different closure type - compiler error! What do you copy from!? 

There isn't any legal way to create a single object of the closure type of the first lambda. Therefore, even if said rule was to be removed, you couldn't create a single instance of my_map_type. Similar problems occur with other "closure type as template argument" scenarios.

Community
  • 1
  • 1
Columbo
  • 60,038
  • 8
  • 155
  • 203
  • *"A Lambda expression shall still not appear in an unevaluated operand"* But it can appear in an unevaluated part of a constant expression. This even allows using lambdas without capture if you don't have a closure object, see http://pfultz2.github.io/Pythy/ – dyp Oct 03 '14 at 01:47
  • @dyp That is not what the question is about. – Columbo Oct 03 '14 at 07:08
  • Yes, but it's what your answer seems to be about (in my eyes). You can pass the type of a lambda to a template, and create a null pointer of the lambda type. If the lambda has no capture, you can call it through that null pointer. That is, you *can* actually pass some lambdas via their type to a template. – dyp Oct 03 '14 at 10:39
  • You mean, calling the `operator()` of the closure type with `this` being zero? That'll invoke undefined behavior since you create a null reference at some point. Could you provide an example please? I think i misunderstand you :) – Columbo Oct 03 '14 at 11:32
  • No, it won't invoke UB, since there's no lvalue-to-rvalue conversion of the dereferenced null pointer. One example is on the page I've linked above. – dyp Oct 03 '14 at 11:52
  • @dyp You're wrong I'm afraid. N3337, [dcl.ref]/5 *[ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be **to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behaviour**. [...]* – Columbo Oct 03 '14 at 12:19
  • Calling `operator()` through a null pointer doesn't create a null reference. However, calling any non-static member function (including operators) through a null pointer is explicitly undefined even if that member function does not access any instance data members. –  Oct 03 '14 at 12:24
  • 1
    9.3.1 Non-static member functions [class.mfct.non-static] p2: "If a non-static member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined." (I suppose you could argue that the wording is defective since a call through a null pointer means that the function is not called on any object, so not called on an object that has a type different from X, but it's pretty widely understood that non-static member functions require an instance.) –  Oct 03 '14 at 12:29
  • @hvd well the *as suggested by Richard Smith from clang* quote suggests that the author is relying implementation specific details, there are a lot of these dark corners unfortunately. I was more than a bit surprised when I discovered the [__builtin_constant_p trick that Richard also has suggested elsewhere](http://stackoverflow.com/a/24400015/1708801) but if compiler implementers are going to suggest them it is hard to blame people for using them. – Shafik Yaghmour Oct 03 '14 at 12:34
  • Well, I've had a hard time finding references to that technique last time I tried, and I'm not doing any better right now. What I can tell is that whether or not dereferencing a null pointer is UB might be subject of an [active CWG issue](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232); and as far as I know, the technique is intended to be portable @hvd makes an interesting argument about the type of the object expression, though. – dyp Oct 03 '14 at 12:38
  • 1
    @ShafikYaghmour There's a difference in supported extensions, and programs where the compiler simply does not detect the undetected behaviour. This particular use of `__builtin_constant_p` is a good example of a supported extension, and by all means to use that if you don't mind that your code will only work with GCC and clang. For the "as suggested by Richard Smith" here, it's not clear to me whether it means that he recommended it, or just that he came up with the idea. If he recommends it, then I have no objections to anyone using that trick with clang. :) –  Oct 03 '14 at 12:40
  • 1
    @dyp There's also [issue 315](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#315), where there has been an explicit statement that calling a static member function through a dereferenced null pointer is well-defined. We can conclude from that that the mere act of dereferencing a null pointer is valid (even if binding it to a reference may not be). –  Oct 03 '14 at 12:42
  • @hvd How do you want to call the member function without doing so? `reinterpret_cast(0)->operator()()`? See [expr.ref]/2: "The expression `E1->E2` is converted to the equivalent form `(*(E1)).E2;` [...]" Which effectively dereferences the null pointer. So how do you want to call it without dereferencing a null pointer? – Columbo Oct 03 '14 at 13:07
  • 1
    As I said, it's unclear to me whether dereferencing the null pointer is actually UB. I would call it as `(*static_cast(nullptr)) ()` or `closure_type_ptr()()`, but I'm not sure if I'd use it at all, considering that it's at least in a grey area of both the Standard and [compiler support](http://llvm.org/bugs/show_bug.cgi?id=20209). – dyp Oct 03 '14 at 13:14
  • @hvd agreed nothing wrong with using supported extensions, I would just prefer they were clearly documented and the documentation was relatively easy to find. – Shafik Yaghmour Oct 03 '14 at 13:14
  • @Loopunroller To be clear, I'm saying that dereferencing a null pointer is well-defined, and that calling an instance member function through a dereferenced null pointer is undefined. I'm saying that `*(T*)0` is valid, but that `(*(T*)0).f()` is invalid, whereas you're saying (correct me if I'm wrong) that `(*(T*)0).f()` is invalid because `*(T*)0` is invalid. –  Oct 03 '14 at 13:22