Search code examples
c++lambdalanguage-lawyerc++20decltype

May types be defined in `decltype` or `sizeof` expressions in C++20?


Since C++20 lambda functions are allowed in unevaluated contexts, and in particular they should be allowed inside decltype and sizeof expressions.

In its turn, lambdas can define some types in their bodies and possible return objects of these types, for example:

using T = decltype( []{ struct S{}; return S{}; } );

[[maybe_unused]] constexpr auto N 
          = sizeof( []{ struct S{}; return S{}; } );

Clang accepts this code, but GCC emits the errors:

error: types may not be defined in 'decltype' expressions
    1 | using T = decltype( []{ struct S{}; return S{}; } );
error: types may not be defined in 'sizeof' expressions
    4 |           = sizeof( []{ struct S{}; return S{}; } );

Demo: https://gcc.godbolt.org/z/9aY1KWfbq

Which one of the compilers is right here?


Solution

  • The restrictions on lambdas in unevaluated contexts that were removed as of P0315R4 means this is not prohibited (albeit a corner case), and thus arguably GCC has a bug here. Particularly the following discussion from the paper is relevant here:

    Furthermore, some questions were raised on the Core reflector regarding redeclarations like this:

    template <int N> static void k(decltype([]{ return 0; }()));
    template <int N> static void k(decltype([]{ return 0; }())); // okay
    template <int N> static void k(int); // okay
    

    These should be valid redeclarations, since the lambda expressions are evaluated, and they neither contain a template parameter in their body nor are part of a full-expression that contains one. Hence, the lambda-expression does not need to appear in the signature of the function, and the behavior is equivalent to this, without requiring any special wording:

    struct lambda { auto operator()() const { return 0; } };
    template <int N> static void k(decltype(lambda{}()));
    template <int N> static void k(decltype(lambda{}())); // okay today
    template <int N> static void k(int); // okay today
    

    The same argument holds also for the OP's example, and (arguably) "the behavior is equivalent to this, without requiring any special wording":

    struct lambda { auto operator()() const { struct S{}; return S{}; } };
    using T = decltype(lambda{});
    

    [[maybe_unused]] constexpr auto N 
              = sizeof( []{ struct S{}; return S{}; } );
    

    Note that the sizeof is simpler, as you are querying the size of the captureless lambda's closure type, and not the size of the local struct S.