3

C++1y offers polymorphic lambdas (i.e., using auto as part of the parameter type):

int         f(int);
double      f(double);
std::string f(const std::string&);

auto funcObj = [](const auto& param){ return f(param); }

Storing the closure generated by the lambda is easy, as shown: just use an auto variable. But suppose I'd like to create a vector of such objects. What type does the vector hold? The usual answer is to use std::function, but that doesn't work in this case, because there is, AFAIK, no such thing as a polymorphic std::function, i.e., this isn't legal in C++1y:

std::vector<std::function<auto(const auto&)>> vecOfPolymorphicClosures;

If this were legal, then you could do things like create a container of callbacks, each of which could be called with any set of arguments and each of which could return a type that's dependent on the types of the arguments passed. The result of any given callback could be stored in an auto variable, at least in theory.

Two questions:

  • Is there a way in C++1y to declare a variable or container that can hold different types of polymorphic lambdas (other than something like boost::any)?
  • Is it even reasonable to hope that such a thing can be possible, or would this kind of thing be incompatible with static typing?
KnowItAllWannabe
  • 12,972
  • 8
  • 50
  • 91
  • for a finite list of types, sure. Infinite? – Yakk - Adam Nevraumont Oct 30 '13 at 00:18
  • Ideally an infinite list, yes. For example, I'd like to be able to create a container that can hold any closure created from a polymorphic lambda taking the same number of arguments. In concept, this is not really that much different from `std::function`, which can hold anything callable with any signature that's compatible with the target signature. – KnowItAllWannabe Oct 30 '13 at 00:32
  • How about `decltype(funcObj)`: `std::vector` or `std::vector`? Probably not. – John Oct 30 '13 at 00:41
  • @John: Your approach would permit holding only one type of closure. That wouldn't be very useful, because every closure has a unique type. – KnowItAllWannabe Oct 30 '13 at 02:18
  • For your purposes, [Boost.Functional/OverloadedFunction](http://www.boost.org/doc/libs/1_54_0/libs/functional/overloaded_function/doc/html/index.html) is to polymorphic functors what Boost.Function is to monomorphic functors. If all else fails, I would recommend Boost.TypeErasure as a starting point for more customized type erasure. – Luc Danton Oct 30 '13 at 13:55

3 Answers3

3

No. Well maybe.

For your particular case, your lambda is just an override set of a single function f known at instantiation. Override set objects can be created and passed around with type erasure without much of a problem. You just need to manually enumerate the overrides and provide it to the override set.

So if your goal is to just have an object that is the override set of f, yes, you can do this. See "Manual" signature overload resolution -- add in some type erasure on top of that mess, and bob is your uncle.

The general case, where you have some auto lambda with arbitrary code within it, no.

The way to envision this problem is imagine a DLL or shared library compiled with your lambda, a second DLL or shared library holding the function like object, and some other DLL or shared library wanting to call it.

The behavior of what happens when you call the function is dependent on the definition of the lambda and the type that you want to call it with to an arbitrary degree.

In order for this to work, a nearly complete run time compilation model would have to be available in both the DLL where the lambda was created, and the DLL where the type it is called with, and that run time compilation model would have to be compatible.

This is both not required by the C++ standard, and would make things far more complex if it was, and would eliminate optimization opportunities.

Now, not all is hopeless.

If there is some fixed list of types you want to support, a polymorphic function signature can be written. This is basically a special case of the 'override set' solution above, and can even be written using it.

On the other hand, if you are willing to type erase the properties of the arguments to your lambda, and type erase, and return some uniform type (be it boost::any or boost::variant or whatever), you can do something. You write up a type erasure object type, and expose it. Then you have a std::function< boost::any(type_erasure_object) >, and the conversion occurs outside the call, and within the call you deal with said type erased object.

Picking overloads using a type erased object is tricky, in that the C++ compiler doesn't help you much with producing a list of overloads to consider. If you collect that list manually, you can even type erase which overload you'll pick.

Pulling that off is possible, but I have not written it before. The alternatives to this are all far easier.

I don't consider the type erased case to solve this problem, as it blocks certain kinds of optimizations. But in theory, it means that you can work with a nearly arbitrary type.

The type erasure object must be exposed to the end user, and it must erase every piece of type information that every lambda you shove into that std::vector needs to know. So this can significantly restrict what lambdas you are storing in your std::vector in some cases.

For an example of how to type erase nearly arbitrary objects, look at boost type erasure.

Finally, what you are asking for is rarely an actual requirement of a problem. You would be best to describe your actual, practical problem, which almost certainly has solutions that are not nearly as esoteric as those above.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Regarding your final paragraph, I think you're overlooking that with monomorphic lambdas, it's straightforward to store them:just use a `function` object with a signature that corresponds to that of the closure created by the lambda. With polymoprhic lambdas, there does not seem to be a way to store them in a container without giving up their support for arbitrary argument types. Given that polymorphic lambdas are a new feature, I think it's important to try to understand the design space of solutions they can offer. That design space appears to exclude containers of polymorphic lambdas. – KnowItAllWannabe Nov 03 '13 at 03:53
  • @KnowItAllWannabe Polymorphic lambda call operator isn't a call operator: it is a `template` for generating call operators. By "storing", you probably mean "type erasing", as storing a polymorphic lambda is easy. Yes, C++ does not support full-context type-erasure of `template` code generation. To do so you'd have to store almost the entire parse tree of both sides for use at any future link time (say, when a DLL is loaded, or even when you link between compilation units). You may need this feature rarely, and the expense would not be trivial. – Yakk - Adam Nevraumont Nov 03 '13 at 13:54
  • If storing a polymorphic lambda is easy, I must be missing something. How do I store a polymorphic lambda as a non-static data member? There's only one type involved, so I should have no need for type erasure. Using a `function` object means I give up the polymorphism of the lambda. – KnowItAllWannabe Nov 03 '13 at 15:17
  • 1
    @KnowItAllWannabe `auto a = [](auto x){};` stores a polymorphic lambda in automatic storage. `templatestruct foo{T f;};` can store a polymorphic lambda in `foo<...>::f`, a non-static data member, and a `make_foo` creation function lets you create it easily. Only type erasure makes it hard (wanting to treat it as a type based only off its polymorphic signature, which is not its type, but a type erased view of it). `std::function` is a type erasure tool that erases most of the type information of a standard lambda: the signature is not the type. – Yakk - Adam Nevraumont Nov 03 '13 at 15:23
  • Use of the template struct to hold the polymorphic lambda is nice, thanks. Upvoted. – KnowItAllWannabe Nov 03 '13 at 16:08
1

The type of a so-called generic lambda is a class-type with a member template operator(). When a conversion is required, the actual type has to be known. For a non-capturing generic lambda, the current draft standard even contains an example:

auto glambda = [](auto a) { return a; };
int (*fp)(int) = glambda;

This is no different from forming a function pointer from an ordinary function template.

For a general generic lambda, I imagine that conversions that expect a callable object will trigger the correct template specialization, so that std::function<int(int)> f(glambda); should work just fine.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • I agree that a `std::function` with a specific signature can hold a polymorphic closure, but a polymorphic closure could be called with an int, a `std::string`, a `std::regex`, etc. I don't want to lose that kind of generality when I store the closure, and `std::function` doesn't provide that, AFAIK. – KnowItAllWannabe Oct 30 '13 at 00:34
  • 1
    @KnowItAllWannabe: Then you're just asking the same question as "how can I store a template in a container so that I can still specialize it arbitrarily". The generic lambda isn't really relevant then. – Kerrek SB Oct 30 '13 at 01:30
  • I'm not asking how to store a template in a container. What I want to store are objects of different types, but each can be called with a potentially unlimited set of argument types. – KnowItAllWannabe Oct 30 '13 at 02:21
  • @KnowItAllWannabe: That is essentially the same thing! – Kerrek SB Oct 30 '13 at 09:10
  • Yes. These are just implicit templates, essentially. – John Oct 30 '13 at 17:06
1

What I want to store are objects of different types, but each can be called with a potentially unlimited set of argument types.

Translation unit A:

// a.cpp
#include <cassert>

std::vector<magical_type> v;

struct lives_in_a { int i; };

// defined in TU B:
void prepare();

int main()
{
    prepare();
    assert( v.front()(lives_in_a { 42 }) == 42 );
}

Translation unit B:

// b.cpp

struct lives_in_b {
    template<typename Anything>
    int operator()(Anything const& a) const
    { return a.i; }
};

void prepare()
{
    // ignore global initialization order fiasco for the sake
    // of the argument
    extern std::vector<magical_type> v;
    v.push_back(lives_in_b {});
}

When and where is lives_in_b::operator()<lives_in_a> instantiated, so that it may be called?

When v.front() is called with argument lives_in_a {}? In that case there is no definition of lives_in_b in sight, so little to even instantiate.

When v.push_back(lives_in_b {}) is called? In that case there's no definition of lives_in_a in sight, so there's isn't much the would-be instantiation could do with.

This is a demonstration that the particular combination of the compilation model of C++ and the way template instantiation work, doesn't allow for that particular wish. It has less to do with static typing.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • I think you mean to call `prepare` in `main`. Setting that aside, there are link-time instantiation models for C++ that would allow this kind of code to both compile and link, and such models are supported by some compilers. Whether they are required to be supported by standard C++, I don't know. Nevertheless, your point is a good one. I'm marking it useful for now, and I'll mark it as the accepted answer, unless further discussion brings out new information. – KnowItAllWannabe Nov 02 '13 at 16:20
  • @KnowItAllWannabe Fixed. Separate compilation of templates has indeed largely been abandoned. Not that in any case it doesn't help with the particular scenario outlined. – Luc Danton Nov 02 '13 at 17:31