69

Found this code which looks to be interesting:

auto a = [](){};

class B : decltype(a)
{
};

I want to know what it does. Can this be useful in any way?

user6511134
  • 559
  • 4
  • 4
  • 3
    This looks interesting but it doesn't compile on `GCC 6.1.0`. What compiler/version are you using? – Galik Jun 25 '16 at 01:22
  • Compiles file on gcc 6.1.1 for me. – Sam Varshavchik Jun 25 '16 at 01:22
  • @SamVarshavchik Actually, you're right, it does compile unless I try to instantiate it. – Galik Jun 25 '16 at 01:25
  • It's clearly useful as something to present in an SO question. Might even be useful to determine some property of the lambda. The question "can this be useful in any way" is too broad; voting to close as such. – Cheers and hth. - Alf Jun 25 '16 at 01:27
  • 11
    I've seen this technique used to create an `overload` function that takes a bunch of lambdas and wraps them all into one type with overloaded function call operators for each parameter list given in the list of lambdas. – chris Jun 25 '16 at 02:20
  • 7
    @chris Please, can you provide an example of implementation? It sounds really interesting. Thank you. – skypjack Jun 25 '16 at 07:46
  • 7
    @Cheersandhth.-Alf It's broad, not too broad. Vote closers seem to constantly misinterpret that – Insane Jun 25 '16 at 08:09
  • This is useful with EBO in class templates. –  Jun 25 '16 at 09:26
  • 1
    @skypjack, I think I was referring to [this one](http://stackoverflow.com/a/18432618/962089). – chris Jun 25 '16 at 12:25
  • @skypjack You can also see my answer, which has been here nearly the entire time and uses the exact same technique that Chris links to, with a clearer use case. – Nir Friedman Jun 25 '16 at 14:25

3 Answers3

37

Well, that code will compile, but the problem is that you will be unable to default construct any object of that class1, because the constructor of the lambda isn't accessible (other than copy/move constructors). The only constructors guaranteed by a lambda type is a defaulted copy/move constructor. And there's no default constructor

[expr.prim.lambda/21]

The closure type associated with a lambda-expression has no default constructor and a deleted copy assignment operator. It has a defaulted copy constructor and a defaulted move constructor ([class.copy]). [ Note: These special member functions are implicitly defined as usual, and might therefore be defined as deleted. — end note ]

or from cppreference:

//ClosureType() = delete;                     //(until C++14)
ClosureType(const ClosureType& ) = default;   //(since C++14)
ClosureType(ClosureType&& ) = default;        //(since C++14)

The history of the lambda constructor being inaccessible dates back to its early days proposal, found here

In section 3, second paragraph, and I quote:

In this translation, __some_unique_name is a new name, not used elsewhere in the program in a way that would cause conflicts with its use as a closure type. This name, and the constructor for the class, do not need to be exposed to the user—the only features that the user can rely on in the closure type are a copy constructor (and a move constructor if that proposal is approved) and the function call operator. Closure types do not need default constructors, assignment operators, or any other means of access beyond function calls. It may be worthwhile for implementability to forbid creating derived classes from closure types. ...

As you can see, the proposal even suggested that creating derived classes from closure types should be forbidden.


1 of course you can copy-initialize the base class with a in order to initialize an object of type B. See this


Now, to your question:

Can this be useful in any way?

Not in your exact form. Your's will only be instantiable with the instance a. However, if you inherit from a generic Callable Class such as a lambda type, there are two cases I can think of.

  1. Create a Functor that calls a group of functors in a given inheritance sequence:

    A simplified example:

    template<typename TFirst, typename... TRemaining>
    class FunctionSequence : public TFirst, FunctionSequence<TRemaining...>
    {
        public:
        FunctionSequence(TFirst first, TRemaining... remaining)
            : TFirst(first), FunctionSequence<TRemaining...>(remaining...)
        {}
    
        template<typename... Args>
        decltype(auto) operator () (Args&&... args){
            return FunctionSequence<TRemaining...>::operator()
                (    TFirst::operator()(std::forward<Arg>(args)...)     );
        }
    };
    
    template<typename T>
    class FunctionSequence<T> : public T
    {
        public:
        FunctionSequence(T t) : T(t) {}
    
        using T::operator();
    };
    
    
    template<typename... T>
    auto make_functionSequence(T... t){
        return FunctionSequence<T...>(t...);
    }
    

    example usage:

    int main(){
    
        //note: these lambda functions are bug ridden. Its just for simplicity here.
        //For correct version, see the one on coliru, read on.
        auto trimLeft = [](std::string& str) -> std::string& { str.erase(0, str.find_first_not_of(' ')); return str; };
        auto trimRight = [](std::string& str) -> std::string& { str.erase(str.find_last_not_of(' ')+1); return str; };
        auto capitalize = [](std::string& str) -> std::string& { for(auto& x : str) x = std::toupper(x); return str; };
    
        auto trimAndCapitalize = make_functionSequence(trimLeft, trimRight, capitalize);
        std::string str = " what a Hullabaloo     ";
    
        std::cout << "Before TrimAndCapitalize: str = \"" << str << "\"\n";
        trimAndCapitalize(str);
        std::cout << "After TrimAndCapitalize:  str = \"" << str << "\"\n";
    
        return 0;
    }
    

    output

    Before TrimAndCapitalize: str = " what a Hullabaloo     "
    After TrimAndCapitalize:  str = "WHAT A HULLABALOO"
    

    See it Live on Coliru

  2. Create a Functor with an overloaded operator()(...), overloaded with all base classes' operator()(...):

    • Nir Friedman has already given a good instance of that in his answer to this question.
    • I have also drafted out a similar and simplified example, drawn from His. See it on Coliru
    • Jason Lucas also demonstrated its practical applications in his CppCon 2014 presentation "Polymorphism with Unions". You can find the Repo here, one of exact location in source code here (Thanks Cameron DaCamara)

Another cool trick: Since the resulting type from make_functionSequence(...) is a callable class. You can append more lambda's or callable to it at a later time.

    //.... As previously seen

    auto trimAndCapitalize = make_functionSequence(trimLeft, trimRight, capitalize);

    auto replace = [](std::string& str) -> std::string& { str.replace(0, 4, "Whaaaaat"); return str; };

    //Add more Functors/lambdas to the original trimAndCapitalize
    auto replaced = make_functionSequence(trimAndCapitalize, replace /*, ... */);
    replaced(str2);
Community
  • 1
  • 1
WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • I don't think this is true, you can construct a B, it will simply have to have a constructor that takes an instance of `a` and constructs its base class using the passed instance. – Nir Friedman Jun 25 '16 at 02:16
  • @NirFriedman. You cannot construct `B`, unless you copy construct the base class with `a` and only the instance `a`, and I mean **only** `a`.... You see, the problem here is that `a` is already an instance of a unique type... Any other lambda created cannot be a type of `a` – WhiZTiM Jun 25 '16 at 02:31
  • I know, that's what I thought I wrote in the original post, sorry if unclear. In any case, i think you originally wrote that B cannot be constructed at all, without any qualifications, which isn't true. Seems like you changed it to say that B cannot be default constructed. That's still not true, you can write a default constructor that initializes the base class using the global a. – Nir Friedman Jun 25 '16 at 02:42
  • @NirFriedman, updated my answer, added a footnote to that effect: [demo](http://coliru.stacked-crooked.com/a/689bbba67ec0fc9b) – WhiZTiM Jun 25 '16 at 03:18
  • There is a proposal to make stateless lambdas default constructible: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0624r2.pdf – alfC Mar 28 '19 at 04:28
8

Lambdas are function objects underneath, with additional syntatic sugar. a evaluates to something like that (with the MyLambda name being a random name, just like when you make namespace {} - namespace name will be random):

class MyLambda {
public:
    void operator()() {
    }
}

So when you inherit from a lambda, what you're doing is inheriting from an anonymous class / structure.

As for usefulness, well, it's as useful as any other inheritance. You can have the functionality of multiple lambdas with multiple inheritance in one object, you can add new methods to it to extend it. I can't think of any real application at the moment, but I'm sure there are many.

Refer to this question for more information.

Community
  • 1
  • 1
Jezor
  • 3,253
  • 2
  • 19
  • 43
6

This can actually be quite useful, but it depends how direct you want to be about the whole thing. Consider the following code:

#include <boost/variant.hpp>

#include <iostream>
#include <string>
#include <unordered_map>

template <class R, class T, class ... Ts>
struct Inheritor : public  T, Inheritor<R, Ts...>
{
  using T::operator();
  using Inheritor<R, Ts...>::operator();
  Inheritor(T t, Ts ... ts) : T(t), Inheritor<R, Ts...>(ts...) {}
};

template <class R, class T>
struct Inheritor<R, T> : public boost::static_visitor<R>, T
{
  using T::operator();
  Inheritor(T t) : T(t) {}
};

template <class R, class V, class ... T>
auto apply_visitor_inline(V& v, T ... t)
{
  Inheritor<R, T...> i(t...);
  return boost::apply_visitor(i, v);
}

int main()
{
  boost::variant< int, std::string > u("hello world");
  boost::variant< int, std::string > u2(5);

  auto result = apply_visitor_inline<int64_t>(u, [] (int i) { return i;}, [] (const std::string& s) { return s.size();});
  auto result2 = apply_visitor_inline<int64_t>(u2, [] (int i) { return i;}, [] (const std::string& s) { return s.size();});
  std::cout << result;
  std::cout << result2;
}

The snippet in your question does not show up in exact form anywhere. But you can see that the types of lambdas are being inferred in apply_visitor_inline. A class is then instantiated that inherits from all of these lambdas. The purpose? We are able to combine multiple lambdas into a single one, for the purpose of things like apply_visitor. This function expects to receive a single function object that defines multiple operator() and distinguish between them based on overloading. But sometimes it's more convenient to define a lambda that operates on each of the types we have to cover. In that case, inheritance from lambdas provides a mechanism for combining.

I got the inline visitor idea from here: https://github.com/exclipy/inline_variant_visitor, though I did not look at the implementation there, so this implementation is my own (but I guess its very similar).

Edit: the originally posted code only worked due to a bug in clang. According to this question (Overloaded lambdas in C++ and differences between clang and gcc), the lookup for multiple operator() in base classes is ambiguous, and indeed the code I posted originally did not compile in gcc. The new code compiles in both and should be compliant. Sadly, there is no way seemingly to do a variadic using statement, so recursion must be used.

Community
  • 1
  • 1
Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • 1
    The lambdas are `copy-constructed`. You need a lambda that is already alive to do so. In your code, do you know what this part does? - `Inheritor(T... t) : T(t)... {}` ..., try replacing `T` with a bunch of lambdas... – WhiZTiM Jun 25 '16 at 02:26
  • I know what it does, I wrote it, it's just the idea I saw elsewhere. T is the type of a lambda, it's equivalent to what the op wrote behind a layer of indirection. – Nir Friedman Jun 25 '16 at 02:40
  • I vaguely remember seeing that idea in a blog post by either Herb Sutter or David Vandevoorde or Dave Abrahams, or two of these, as an example in a general discussion of overloading. – Cheers and hth. - Alf Jun 25 '16 at 09:08
  • Also, somewhat more mundane, allowing you to inherit from a closure type means that you can EBO empty ones. – T.C. Jun 25 '16 at 18:11