28

I'm trying to use a lambda as a static member, like this:

struct A
{
    static constexpr auto F = [](){};
};


int main()
{
    A::F();
    return 0;
}

Is this even correct C++11 code? On clang, I get this error:

error: constexpr variable 'F' must be initialized by a constant
      expression
    static constexpr auto F = [](){};
                              ^~~~~~

It seems in clang, lambdas aren't considered a constant expression. Is this correct? Perhaps they haven't fully implemented lambdas yet in clang because gcc 4.7 seems to allow it as a constexpr, but it give another error:

error: ‘constexpr const<lambda()> A::F’, declared using local type ‘const<lambda()>’, is used but never defined

I'm not sure, I understand what that means. It seems to correctly deduce the type of the lambda, but it only declares it and not define it. How would I go about defining it?

Mark Hurd
  • 10,665
  • 10
  • 68
  • 101
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
  • 11
    Can I be super unhelpful and ask, uh, "Why on earth would you want to do this?" What would this construction do that a normal member function would not? – Rook Jul 30 '12 at 16:57
  • 1
    @Rook Save him to write the return type and we can throw the `static constexpr auto` noise into a macro. – pmr Jul 30 '12 at 17:02
  • Because type deduction is much better with lambdas than member functions. – Paul Fultz II Jul 30 '12 at 17:02
  • Can you show us the real problem you're trying to solve? – Mark B Jul 30 '12 at 17:03
  • 3
    @Paul: so is the actual issue here that you're working with functions that have particularly crazy and unwieldy return types? – Rook Jul 30 '12 at 17:06
  • Ultimately, I want to use lambdas inside polymorphic functions, and an example may not seem directly related to the question. But [here](https://github.com/pfultz2/Linq/wiki/Linq-inside-polymorphic-function) is a discussion of the problem. – Paul Fultz II Jul 30 '12 at 17:24
  • 2
    @Paul: ahh, that makes a lot more sense now. It looks to me a lot like you are operating at the bleeding edge of C++, so you might have to wait for compilers to catch up with you. I note VC++ 2012 can't even manage `constexpr` yet, either. What you're doing doesn't _look_ like it contravenes the standard, but I may well be misreading things. – Rook Jul 30 '12 at 17:41

2 Answers2

18

This code is ill-formed. A constexpr variable is required to be initialized by a constant expression, and [expr.const]p2 says:

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression [...]:

  • a lambda-expression

GCC is therefore incorrect to accept this code.

Here's one way to give a class a static data member of lambda type:

auto a = []{};
struct S {
  static decltype(a) b;
};
decltype(a) S::b = a;
Richard Smith
  • 13,696
  • 56
  • 78
  • _GCC is therefore incorrect to accept this code._ I think it's not that simple. Is diagnostic/error required for this case? There are many ill-formated programs that give no compiler error/warning (and for good reason) – Johan Lundberg Jul 31 '12 at 10:32
  • Constexpr aside, is there no possible way to create a lambda as a static member to a class in C++11? – Paul Fultz II Jul 31 '12 at 19:19
  • I've interpreted Paragraph 1 to describe what is a constant expresssion (but that's grammar, with no attached semantics, even in the following paragraphs). What you're quoting is paragraph 2, which I've taken to describe core constant expressions (with actual semantics). The paragraph about initializing a `constexpr` object only mentions: "Otherwise, or if a constexpr specifier is used in a reference declaration, every full-expression that appears in its initializer shall be a constant expression." What am I missing? – Luc Danton Aug 01 '12 at 11:11
  • 2
    @LucDanton Constant expressions are defined in terms of core constant expressions, so p2 is relevant. See p3. The different of "constant expression" and "core constant expression" is not "syntax" vs "semantics" but, as far as I can remember, "actual compile-time known value or reference" vs "intermediate result in computing a constant expression". So, the address of a non-static local variable is a valid core constant expression. But as that address's value is not really known at compile time, it is not an address constant expression. – Johannes Schaub - litb Aug 02 '12 at 20:19
  • 1
    @JohanLundberg Yes, a diagnostic is required for every rule, including this one, unless otherwise stated. See `[intro.compliance]p2`. – Richard Smith Aug 03 '12 at 21:25
  • @Paul I've added an example of a way to get the desired effect. – Richard Smith Aug 03 '12 at 21:30
  • @RichardSmith Thanks, that will work as long as the class is not templated. I would like to have the lambda depend on a template parameter. Which I didn't really specify in the question. – Paul Fultz II Aug 03 '12 at 23:25
  • 2
    @Paul I think you're out of luck. There are some tricks to get the lambda expression inside the template and extract its type, such as `template std::remove_reference::type *addr(T &&t) { return &t; } struct S { constexpr static auto *p = false ? addr([]{}) : nullptr; static decltype(*p) lambda; };`, but even then it's not possible to provide an initializer for `S::lambda`. – Richard Smith Aug 06 '12 at 04:30
  • @RichardSmith Wow, I didn't realize I could use the tennary operator to trick the compiler in skipping over non-constexpr subexpressions. I think I will be able to make that work, but instead of a static member, it could just be a static function(since I have to initialize it). – Paul Fultz II Aug 06 '12 at 15:48
  • @Paul even if you were able to do what you want, I think your class would be pretty useless because once you use it in more than one translation unit, you violate the rule that states that the type of a variable or function must be the same in all declarations of it (violating it effectively causes undefined behavior). – Johannes Schaub - litb Sep 06 '12 at 21:23
2

You can make it work, in clang 3.4, as long as the lambda doesn't capture anything. The idea is directly from Pythy .

#include <type_traits>
#include <iostream>
template<typename T>
auto address(T&& t) -> typename std:: remove_reference<T> :: type *
{
        return &t;
}

struct A
{
        static constexpr auto * F = false ? address(

                [](int x){ std:: cout << "It worked. x = " << x << std:: endl;

                }
        ) : nullptr; // a nullptr, but at least its *type* is useful
};


int main()
{
    (*A::F)(1337); // dereferencing a null. Doesn't look good
    return 0;
}

There are two potentially controversial bits here. First, there's the fact that A::F is constexpr, but it has a lambda in its definition.

That should be impossible right? No. A ternary expression b ? v1 : v2 can be a constexpr without requiring all three of b, v1, v2 to be constexpr. It is sufficient merely that b is constexpr along with one of the remaining two (depending on whether b is true or false. Here b is false, and this selects the final part of the ?:, i.e. nullptr.

In other words false ? a_non_constexpr_func() : a_constexpr_func() is a constexpr. This appears to be the interpretation in clang anyway. I hope this is what's in the standard. If not, I wouldn't say that clang "should not accept this". It appears to be a valid relaxation of the rule. The unevaluated part of a ?: is unevaluated and therefore it constexpr-ness shouldn't matter.

Anyway, assuming this is OK, that gives us a nullptr of the correct type, i.e. the type of a pointer to the lambda. The second controversial bit is (*A::F)(1337); where we are dereferencing the null pointer. But it is argued by the page linked above that that is not a problem:

It appears that we are derefencing a null pointer. Remember in C++ when dereferencing a null pointer, undefined behavior occurs when there is an lvalue-to-rvalue conversion. However, since a non-capturing lambda closure is almost always implemented as an object with no members, undefined behavior never occurs, since it won't access any of its members. Its highly unlikely that a non-capturing lambda closure could be implemented another way since it must be convertible to a function pointer. But the library does statically assert that the closure object is empty to avoid any possible undefined behavior.

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • That page is missing the point on Undefined Behavior. It happens when you dereference a null pointer. Now the Undefined Behavior may not be _noticeable_ , for instance because the lambda has no members, but that's an implementation detail. It's UB because the standard says so. – MSalters Feb 27 '14 at 19:24
  • 1
    @MSalters That is an oversimplification. The standard doesn't say that `(*(E1)).E2` is undefined behaviour when it is null. I believe undefined behaviour only occurs when there is an lvalue-to-rvalue conversion. So `&*(p=0)` is not undefined behavior. See [here](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232) for a discussion. – Paul Fultz II Feb 28 '14 at 02:23
  • THANK YOU FOR THIS! I needed to get the return type of a lambda taking a known template parameter, and this seems to be the only way to do so (at least as of C++11). – Matthew Apr 03 '18 at 17:16