Proposal for lazy evaluation of C++ function arguments

C

clintonmead

/*

(I've commented this code so it should compile just by copying/pasting the message. You'll also need boost.)

Lets say we want to create a boost::variant, with a few options. For example, we could be creating a filestream, but our options might be a dummy filestream, stdout or a real file stream. But for simplicity in this case, let's assume the options are 'int', 'std::string' and 'char'. Lets also assumewe have functions which return appropriate values of each type, like so:

*/

#include <string>

int f() { return 42; }
std::string g() { return "The meaning of life"; }
char h() { return '!'; }

/*

Now we'd like to make a variant like this:

auto x = make_variant(n, f(), g(), h());

For n = 0, x = 42, for n = 1, x = "The meaning of life" and for x = 2, n = '!'.

Also, this way, the type of the boost::variant is inferred.

However, the problem with this is that all of f(), g() and h() are evaluated. Not much of a problem in this case, but if f(), g() or h() were complicated this could be an issue.

If we want to delay evaluation, we might do this:

auto x = make_variant(n, []{return f();}, []{return g();}, []{return h();});

This looks a bit ugly now, but we can now write 'make_variant' like below:

*/

#include <boost/variant.hpp>
#include <functional>
#include <stdexcept>

template <class RETURN_T>
RETURN_T make_variant_worker(int n)
{
throw std::runtime_error("Invalid selection");
}

template <class RETURN_T, class FIRSTARG, class... ARGS>
RETURN_T make_variant_worker(int n, const FIRSTARG& first_arg, const ARGS&.... args)
{
return n == 0 ? first_arg() : make_variant_worker<RETURN_T>(n - 1, args...);
}

template <class... ARGS>
auto make_variant(int n, const ARGS&... args) -> boost::variant<decltype(args())...>
{
return make_variant_worker<boost::variant<decltype(args())...>>(n, args....);
}

/*

We can then use the 'make_variant' function as follows:

*/

int main()
{
int n;
std::cin >> n;
auto x = make_variant(n, []{return f();}, []{return g();}, []{return h();});
std::cout << x << std::endl;
}

/*

However, the call to 'make_variant' is a bit ugly. What I'd like to be ableto do is somewhere in the 'make_variant' function put an attribute on the function arguments that implicitly wraps all the arguments with "[] { return arg; }", i.e. delays their evaluation. Hence, I could call:

make_variant(n, f(), g(), h());

Without evaluating f(), g() or h() unnecessarily.

This isn't without precedent in C++, the non-overridden "&&", "||" and "?:"all don't necessarily evaluate all of their arguments. Such a feature would allow one not only to write a 'make_variant' function without splatteringlambdas all throughout the code, but also create short circuit operators for "&&" and "||" (say, for three value logic), and functions like "if_then_else" that behave like their builtin equivalents.

I'm not sure how to make this a formal proposal, even if I did I wouldn't know how to express the details. However, I think it would be fairly simple to implement because it's effectively just syntactic sugar, particularly when the call is occurring internally in one compilation unit. I'm not sure if I'm reinventing the wheel here, if this can already be done in C++11 I'd be interested to see how, otherwise if someone wants to take this up that'smore experienced than me with these things I'd be happy with that too.

Regards,

Clinton Mead

*/
 
V

Victor Bazarov

/*

(I've commented this code so it should compile just by copying/pasting the message. You'll also need boost.)

Lets say we want to create a boost::variant, with a few options. For example, we could be creating a filestream, but our options might be a dummy file stream, stdout or a real file stream. But for simplicity in this case, let's assume the options are 'int', 'std::string' and 'char'. Lets also assume we have functions which return appropriate values of each type, like so:

*/

#include <string>

int f() { return 42; }
std::string g() { return "The meaning of life"; }
char h() { return '!'; }

/*

Now we'd like to make a variant like this:

auto x = make_variant(n, f(), g(), h());

For n = 0, x = 42, for n = 1, x = "The meaning of life" and for x = 2, n = '!'.

Also, this way, the type of the boost::variant is inferred.

However, the problem with this is that all of f(), g() and h() are evaluated. Not much of a problem in this case, but if f(), g() or h() were complicated this could be an issue.

If we want to delay evaluation, we might do this:

auto x = make_variant(n, []{return f();}, []{return g();}, []{return h();});

This looks a bit ugly now, but we can now write 'make_variant' like below:

*/

#include <boost/variant.hpp>
#include <functional>
#include <stdexcept>

template <class RETURN_T>
RETURN_T make_variant_worker(int n)
{
throw std::runtime_error("Invalid selection");
}

template <class RETURN_T, class FIRSTARG, class... ARGS>
RETURN_T make_variant_worker(int n, const FIRSTARG& first_arg, const ARGS&... args)
{
return n == 0 ? first_arg() : make_variant_worker<RETURN_T>(n - 1, args...);
}

template <class... ARGS>
auto make_variant(int n, const ARGS&... args) -> boost::variant<decltype(args())...>
{
return make_variant_worker<boost::variant<decltype(args())...>>(n, args...);
}

/*

We can then use the 'make_variant' function as follows:

*/

int main()
{
int n;
std::cin >> n;
auto x = make_variant(n, []{return f();}, []{return g();}, []{return h();});
std::cout << x << std::endl;
}

/*

However, the call to 'make_variant' is a bit ugly. What I'd like to be able to do is somewhere in the 'make_variant' function put an attribute on the function arguments that implicitly wraps all the arguments with "[] { return arg; }", i.e. delays their evaluation. Hence, I could call:

make_variant(n, f(), g(), h());

Without evaluating f(), g() or h() unnecessarily.

This isn't without precedent in C++, the non-overridden "&&", "||" and "?:" all don't necessarily evaluate all of their arguments. Such a feature would allow one not only to write a 'make_variant' function without splattering lambdas all throughout the code, but also create short circuit operators for "&&" and "||" (say, for three value logic), and functions like "if_then_else" that behave like their builtin equivalents.

I'm not sure how to make this a formal proposal, even if I did I wouldn't know how to express the details. However, I think it would be fairly simple to implement because it's effectively just syntactic sugar, particularly when the call is occurring internally in one compilation unit. I'm not sure if I'm reinventing the wheel here, if this can already be done in C++11 I'd be interested to see how, otherwise if someone wants to take this up that's more experienced than me with these things I'd be happy with that too.

Regards,

Clinton Mead

*/

Without reading too carefully, my first reaction is, "it's not lazy
evaluation, it's short-circuit evaluation", IOW, you don't delay, you
avoid evaluating what's not needed. Second reaction, the language
already has this:

(n == 0 ? f() : n == 1 ? g() : h())

which will not evaluate f() or h() if n == 1.

There might be something to the "arithmetic ?:" construct, I am not sure
what problem you're going to be solving with it that can't be solved by
other means, so it would just be a syntactic sugar. But consider a
lambda like this:

[](int n) { switch (n) {
case 0: return f();
case 1: return g();
case 2: return h(); } throw "bad value"; };

All you need is the ability to specify the return value type of such
lambda to be 'super-auto' if the functions 'f', 'g', 'h' all return
different types.

Is that what you're talking about?

V
 
C

Clinton Mead

/*

(I've commented this code so it should compile just by copying/pasting the message. You'll also need boost.)

Lets say we want to create a boost::variant, with a few options. For example, we could be creating a filestream, but our options might be a dummy file stream, stdout or a real file stream. But for simplicity in this case,let's assume the options are 'int', 'std::string' and 'char'. Lets also assume we have functions which return appropriate values of each type, like so:



#include <string>

int f() { return 42; }
std::string g() { return "The meaning of life"; }
char h() { return '!'; }

Now we'd like to make a variant like this:
auto x = make_variant(n, f(), g(), h());
For n = 0, x = 42, for n = 1, x = "The meaning of life" and forx = 2, n = '!'.
Also, this way, the type of the boost::variant is inferred.
However, the problem with this is that all of f(), g() and h() are evaluated. Not much of a problem in this case, but if f(), g() or h() were complicated this could be an issue.
If we want to delay evaluation, we might do this:
auto x = make_variant(n, []{return f();}, []{return g();}, []{return h();});
This looks a bit ugly now, but we can now write 'make_variant' like below:

#include <boost/variant.hpp>
#include <functional>
#include <stdexcept>

template <class RETURN_T>
RETURN_T make_variant_worker(int n)

throw std::runtime_error("Invalid selection");


template <class RETURN_T, class FIRSTARG, class... ARGS>
RETURN_T make_variant_worker(int n, const FIRSTARG& first_arg, const ARGS&... args)

return n == 0 ? first_arg() : make_variant_worker<RETURN_T>(n - 1, args...);


template <class... ARGS>
auto make_variant(int n, const ARGS&... args) -> boost::variant<decltype(args())...>

return make_variant_worker<boost::variant<decltype(args())...>>(n, args...);




We can then use the 'make_variant' function as follows:



int main()

int n;
std::cin >> n;
auto x = make_variant(n, []{return f();}, []{return g();}, []{return h();});
std::cout << x << std::endl;

However, the call to 'make_variant' is a bit ugly. What I'd like to be able to do is somewhere in the 'make_variant' function put an attribute on the function arguments that implicitly wraps all the arguments with "[] { return arg; }", i.e. delays their evaluation. Hence, I could call:
make_variant(n, f(), g(), h());
Without evaluating f(), g() or h() unnecessarily.
This isn't without precedent in C++, the non-overridden "&&", "||" and "?:" all don't necessarily evaluate all of their arguments. Such a feature would allow one not only to write a 'make_variant' function without splattering lambdas all throughout the code, but also create short circuit operators for "&&" and "||" (say, for three value logic), and functions like "if_then_else" that behave like their builtin equivalents.
I'm not sure how to make this a formal proposal, even if I did I wouldn't know how to express the details. However, I think it would be fairly simple to implement because it's effectively just syntactic sugar, particularly when the call is occurring internally in one compilation unit. I'm not sure if I'm reinventing the wheel here, if this can already be done in C++11 I'd be interested to see how, otherwise if someone wants to take this up that's more experienced than me with these things I'd be happy with that too.

Clinton Mead



Without reading too carefully, my first reaction is, "it's not lazy

evaluation, it's short-circuit evaluation", IOW, you don't delay, you

avoid evaluating what's not needed. Second reaction, the language

already has this:



(n == 0 ? f() : n == 1 ? g() : h())

I'm pretty sure this doesn't work in the example I provided (or the real life situation which inspired this) as f(), g() and h() are different types.
which will not evaluate f() or h() if n == 1.



There might be something to the "arithmetic ?:" construct, I am not sure

what problem you're going to be solving with it that can't be solved by

other means, so it would just be a syntactic sugar. But consider a

lambda like this:



[](int n) { switch (n) {

case 0: return f();

case 1: return g();

case 2: return h(); } throw "bad value"; };



All you need is the ability to specify the return value type of such

lambda to be 'super-auto' if the functions 'f', 'g', 'h' all return

different types.



Is that what you're talking about?

No, not really. boost::variant is kind of like a "super-auto", but I'm justusing boost::variant as a way to illustrate my proposal, which could be used in other situations as well where all the types are the same, such as a TriBool class. i.e. If a TriBool class had values TriTrue, TriFalse and TriUnknown, and we had:

TriBool f() { return TriTrue; }
TriBool g() { throw "OH NO"; }

f() || g() should return TriTrue, not throw.

We could write

TriBool operator||(TriBool x, std::functional<TriBool()> f_y)
{
if (x == TriTrue) { return TriTrue; }
else
{
TriBool y = f_y();
if (y == TriTrue) { return TriTrue; }
else if (x == TriFalse && y == TriFalse) { return TriFalse; }
else { return TriUnknown; }
}
}

Then we could do the following:

f() || [] { return g(); }

And g() would only be evaluated if f() was not true.

I'd like to be able to just write:

f() || g()

in the client code, like one does for non-overloaded operators. Wrapping inthe lambda would be implicit due to some attribute, just like it's implicit the second argument of a non-overloaded || or && is not evaluated until the first is examined.

So there's two examples where this proposal would be useful, firstly constructing a boost::variant, but also short circuit non-boolean logic. I'm surethere are plenty other examples though.

Clinton
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,015
Latest member
AmbrosePal

Latest Threads

Top