int a = ({
int y = foo ();
int z;
if (y > 0)
z = y;
else z = - y;
z; // the output of the statement expression
});
int foo()
{
return 1 + ({
int a = boo();
if(!a)
return -1; // returns from foo()
a;
});
}
int foo()
{
return 1 + [!]{
int a = boo();
if(!a)
return -1; // returns from foo()
a;
}();
}
void evil()
{
// Inject "return 5;" into whomever calls evil()
[!!] { return 5; }();
}
int foo()
{
evil(); // as if "return 5;"
}
void evil()
{
// Inject "return 5;" into whomever calls evil()
[!!] { return 5; }();
}
What do people think?
If it can't inject in some given use case, it fails to compile with a suitable error message.
injectable string not_as_evil(){ if(...) return 5; else expr_return "foo"s;}
int hear_no_evil(){ string s1 = inject not_as_evil(); ...
string s2 = inject [] injectable { if(...) return 4; else expr_return "bar"s; }();}
void evil!()
{
// Inject "return 5;" into whomever calls evil!()
return 5;
}
int foo()
{
evil!(); // as if "return 5;"
}
On Wednesday, October 11, 2017 at 1:55:56 AM UTC+1, Nicol Bolas wrote:On Tuesday, October 10, 2017 at 7:02:48 PM UTC-4, Niall Douglas wrote:What do people think?I think I've seen this before. I didn't much care for it then either. Or at least, the "I'm can pass 'functions' around that force the caller to return, without the caller having any clue this can happen" part.
For the record, I feel no love for GCC type statement expressions. They are too limited to replace C macros, specifically, you can't inject variable creation with them. They also have very mixed quality of implementation depending on the compiler. That proposal you linked to was very complicated. And GCC type statement expressions as-is can't fully replace C macros, which makes that proposal dead for me just on that.The evil lambda thing I proposed isn't complex, it literally inserts its contents into however many stack frames higher than its call site. Now that's wrong on so many levels. But I'm exploring some options here. Constructive suggestions for how to better replace the "inserting boilerplate" use case for C macros are welcome.
Niall
On Wednesday, October 11, 2017 at 2:08:31 AM UTC+1, Todd Fleming wrote:On Tuesday, October 10, 2017 at 8:28:20 PM UTC-4, Niall Douglas wrote:If it can't inject in some given use case, it fails to compile with a suitable error message.What about making it explicit like try expressions?
injectable string not_as_evil(){if(...)return 5;elseexpr_return "foo"s;}int hear_no_evil(){string s1 = inject not_as_evil();...string s2 = inject [] injectable {if(...)return 4;elseexpr_return "bar"s;}();}I like the idea of marking these code objects as being of "injectable" type rather than subverting the lambda syntax. I only chose the latter because I know WG21 dislikes adding new keywords.
I don't think the explicit inject is good though. Boilerplate insertion ought to be quick to type. How about copying Rust:
void evil!()
{
// Inject "return 5;" into whomever calls evil!()
return 5;
}
int foo()
{
evil!(); // as if "return 5;"
}So, if your function name ends with '!', it injects its contents into the calling scope.And you cannot call an injecting function without its postfix of '!' which clearly indicates that this thing will be injecting code here.Thoughts?
Niall
The injection operator ->, currently being used in the metaclass proposal can be adapted for code injection as a form forced inline function. For example
->foo(a);
Copies the body of foo into the current context while doing the necessary substitution of the argument if there is any
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/63d5cecd-c442-7077-90f5-066b0bc84697%40otoy.com.
The injection operator ->, currently being used in the metaclass proposal can be adapted for code injection as a form forced inline function. For example
->foo(a);
Copies the body of foo into the current context while doing the necessary substitution of the argument if there is any
template<typename V, typename E>
V try_(expected<V, E> x) {
if(x)
return *x;
else
parent_return x;
}
expected<int, error> test_expected() {
auto x = ->try_(f1());
auto y = ->try_(f2(x + 7));
return ->try_(f4(x + y)) / ->try_(f5(x / y));
}
The injection operator ->, currently being used in the metaclass proposal can be adapted for code injection as a form forced inline function. For example
->foo(a);
Copies the body of foo into the current context while doing the necessary substitution of the argument if there is anyIt looks like metaclass -> needs these changes to work for the expression try case:
- Work in a non-constexpr context
- Work within expressions
- Some way to indicate the expression result (return?) vs. forcing parent to return (parent_return?)
template<typename V, typename E>
V try_(expected<V, E> x) {
if(x)
return *x;
else
parent_return x;
}
expected<int, error> test_expected() {
auto x = ->try_(f1());
auto y = ->try_(f2(x + 7));
return ->try_(f4(x + y)) / ->try_(f5(x / y));
}
template<typename V, typename E>
V try!(expected<V, E> x) {
if(x)
return!(*x); // The output of this "macro"
else
return x; // Injects "return x;" into the caller
}
expected<int, error> test_expected() {
auto x = try!(f1());
auto y = try!(f2(x + 7));
return try!(f4(x + y)) / try!(f5(x / y));
}
But I also find the above syntax ugly, plus I find the "try_" unfortunate.The '!' based approach enables a separate namespace, thus allowing one to define to do!, try!, return! and so on without collision with C++ keywords. So, repeating your example:
template<typename V, typename E>
V try!(expected<V, E> x) {
if(x)
return!(*x); // The output of this "macro"
else
return x; // Injects "return x;" into the caller
}
expected<int, error> test_expected() {
auto x = try!(f1());
auto y = try!(f2(x + 7));
return try!(f4(x + y)) / try!(f5(x / y));
}
Niall
auto z = [!](bool a)
{
if (a)
return return 42; //yup double return, could be `return break;` too?
else
return;
} -> { void, int }; //we can return void or double return int
{void, int, int} tripleReturn();
{int, int} doulbeReturn()
{
register tripleReturn(); //ok can return from `doulbeReturn` or function that call it
}
int normalReturn()
{
register tripleReturn(); //error, too many return levels in tripleReturn!
int i = register doubleReturn();
int j = register []{ register tripleReturn(); }(); //ok, lambda implicitly `->{int, int}` and `tripleReturn` can return from `normalReturn`
}
return!(*x) already has a meaning. I'm OK with the language standard redefining keyword meaning with ! when there's no existing conflict, but having some keywords with ! be user defined terrifies me.
template<typename V, typename E>
V try#(expected<V, E> x) {
if(x)
return#(*x); // The output of this "macro"
else
return x; // Injects "return x;" into the caller
}
expected<int, error> test_expected() {
auto x = try#(f1());
auto y = try#(f2(x + 7));
return try#(f4(x + y)) / try#(f5(x / y));
}
On Tuesday, October 10, 2017 at 8:28:20 PM UTC-4, Niall Douglas wrote:If it can't inject in some given use case, it fails to compile with a suitable error message.What about making it explicit like try expressions?
injectable string not_as_evil(){if(...)return 5;elseexpr_return "foo"s;}
We currently have at least two active proposals related to "messing with the caller's frame":- Gor's Coroutines TS with operator "co_await" and operator "co_yield"- Niall and Vicente's proposal with operator "try"(I think we have recently encountered another similar case, but I'm blanking on it right now.)This idea of somehow increasing the scope of things an expression can do with its enclosing frame, via a named operator or some special syntax, is very powerful. I would like to see it approached systematically, and I would like to see the systematic approach attempted before anyone tries to merge the Coroutines TS into C++2a, because by then it will have become too late.
return!(*x) already has a meaning. I'm OK with the language standard redefining keyword meaning with ! when there's no existing conflict, but having some keywords with ! be user defined terrifies me.Oh my yes you're right. And that's going to be an issue with using any token which is valid in an expression in C++.The only two safe characters are therefore '?' or ':' as those can never be ambiguous. Or use a new character not legal in a valid identifier.Speaking of which, it turns out that the C preprocessor is required to pass through a '#' when not the first non-whitespace token in a line. And the compiler always errors out if it sees stray # after the preprocessor. So this is I think is the solution:
template<typename V, typename E>
V try#(expected<V, E> x) {
if(x)
return#(*x); // The output of this "macro"
else
return x; // Injects "return x;" into the caller
}
That's backwards. If `#` means "cause non-local effects", then `return#` should mean to inject that into the caller. Unadorned `return` should return from the function with that value.
If "return#" and "try#" is not a single token then I believe the user is
allowed to type "#" separately on the next line from the preceeding
keyword, which will break the preprocessor.
That's backwards. If `#` means "cause non-local effects", then `return#` should mean to inject that into the caller. Unadorned `return` should return from the function with that value.I decided for brevity and simplicity to eliminate the ability for native C++ macros to return anything.
> I'm no parsing expert, but in parsers I've written in the past you would
> simply include '#' as a valid character in an identifier so long as it
> is not the first character.
But in this case '#' is not part of an identifier, it's a separate
token. The fact that it breaks preprocessor in some cases contradicts
the experience we have with any other token in C/C++, which you can
format however you like, as long as the tokens can be parsed unambiguously.
--
Matthew
(Oh... another matter... I think such functions either can't yield a
value normally, which makes them less useful than statement expressions,
or they would need to specify both the yielded and
possibly-returned-through-caller return types.)
On 2017-10-12 13:08, Nicol Bolas wrote:
> On Thursday, October 12, 2017 at 12:43:12 PM UTC-4, Marcin Jaczewski wrote:
>> As I said in other post, this need be part of signature, with this ABI
>> could allow passing more return address to function to handle additional
>> exit paths.
>
> That's a pretty substantial change to the idea. It's originally specified
> as being "macro"-like, with token pasting and the like. It's not supposed
> to get to the level of being part of an ABI.
It *has* to be part of the ABI, and no, this isn't a departure.
The
original idea implicitly had the ABI "I am not an emitted function".
> What you're now talking about is a thing where a function can return in odd
> ways.
Well... yeah. That's the only way it *can* work if you're going to stick
it in a std::function, or allow it to be called when it can't be
actually inlined.
> At which point, it's really just an oddball form exception handling [...]
...except that the compiler is expected to inline where possible to
avoid all the nasty overhead of exceptions, and even when not, the
"unwinding" mechanism would look much different.
This is much closer to
expected+try (which, I believe, was the intention), except the "failing
try" can return anything.
> Indeed, object destructors might even want to do different things
> (transaction rollbacks) if they're destroyed due to unwinding from these
> kinds of returns, relative to regular scope exits.
That wasn't part of the proposal,
and I'm not *at all* convinced it should be.
auto _ = scope_fail(...);
auto val = try#(some_expression);
--
Matthew