Date: Mon, 06 Jul 2026 16:45:09 +0000
Hi Arthur,
Thank you for the constructive feedback. I'll extend some examples and explanations based on it.
Lambda functions are a great tool, and we should use them. I have no doubts here. This proposal comes as additional functionality. It'll not help with function overloads (which we should probably avoid in general, but this is out of scope). It can be solved to a certain extent with the proposed placeholder, or simply by using lambda functions.
One of the major issues I see here is that lambda syntax can be too noisy for this specific scenario.
For example, it's correct that you can use "[&]" and let the compiler do the job, but there might be different code style preferences in a company and/or static analysis tooling setup. So in the worst-case scenario, it can turn into "[&a, &b, &c]" even if this is not necessary.
Same for the first argument; it's not always possible to use, say, "auto&& a". It can be a full type name for the sake of readability and a long argument name.
Reducing "[/*potentially long*/](/*potentially long*/) { return /*a function call of args*/; }" to "function name + args" is already not entirely bad in terms of readability. I use "potentially long" here because real names are usually longer than a single symbol.Also, code style might disallow one-line short lambdas. In this situation, even a chain of three "and_then"-like calls may look complicated.
To the "std::placeholders::value" topic, I proposed introducing some constraints: https://github.com/vt4a2h/ipa#add-new-constraints. I.e. you can pass zero or one of such objects, which I think is a sane restriction.
However, I didn't think about this case: "o.and_then(f, std::placeholders::value);". Well spotted! We can add one more restriction, like "if there is a single argument to forward, it cannot be a placeholder". In this case, it should be better in terms of assumptions about the meaning of "o.and_then(f)".
> Even if it weren't for that, the programmer might think it's weird that he can write `f(x, 42)` as `(f, std::placeholders::value, 42)`, but that there's no way to write `f(&x, 42)` or `f(x + 42)`. You could try to add one. At this point you're reinventing std::bind and all its wacky pitfalls. We invented lambdas in order toeliminatethis mess of weird DSLs from C++. Don't bring it back!
I don't think I fully get this point to be honest. Assume we have "expected<int, E>" and "f(int) -> expected<int, E>". With the new restriction from above this code will be ill-formed: "e.and_then(f, std::placeholders::value)". Ditto for "e.and_then(f, std::placeholders::value, 42)" (to many arguments to invoke "f").
A more interesting scenario is if we want to mutate a value and have a function like: "f(int&) -> expected<void, E>". It will not work in the existing proposal, because I used just "T". However, if I use something like: "using fwd_t = decltype(std::forward_like<Self>(std::declval<T>()));" (1), it'll be possible to invoke "e.and_then(f)". I'm not 100% sure whether this is something the API should allow, but it looks nice at a glance, and the behavior depends on the variable's category and the function parameters.
Now, assume we have a function with a different signature: "f(int, int&) -> expected<void, E>". Having (1) implemented, this code will work as expected: "e.and_then(f, 1, ipa::placeholders::value)". Same with "bind": "e.and_then(std::bind(f, 1, std::placeholders::_1))" or a lambda.
If we want to mutate not "e", but say, a variable "v", then we don't need to use placeholder "e.and_then(f, v)". For "bind", it looks more wordy: "e.and_then(std::bind_back(f, std::ref(v)))".
I'll add more examples to the repo.
Generally speaking, I'd like "ipa::placeholders::value" (or a similar abstraction) to have a clear meaning: "it'll be replaced with a contained value while keeping the value category". No other meanings or extra operations permitted.
> I mean not a survey of humans' opinions, but a survey of real codebases that could use a mechanical translation of the existing `.and_then` into this style
Good idea! Do you have any good open-source projects in mind that you may suggest exploring?
To Q&A, I think I answered everything. Here is the rest:
> lambdas don't have names.
Correct, my bad wording. I'll fix this. What I meant was:
auto a = [] () {};
auto b = [] () {};
auto c = [] () {};
e.and_then(a)
.and_then(b)
.and_then(c);
> "A user has to write more (repeated) code" — example needed. Are you talking about the boilerplate of `[&]() { return`? (If not:shouldyou have?)
Yes, this one and what I wrote in the first paragraphs of the answer. Will add more examples.
On Monday, July 6th, 2026 at 03:46, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]> wrote:
> On Sun, Jul 5, 2026 at 5:46 PM Vitaly Fanaskov via Std-Proposals <std-proposals_at_[hidden]> wrote:
>
>> This paper aims to extend monadic operations of std::expected and std::optional by allowing taking extra arguments. These arguments will be forwarded to the given function, passed after the first argument with the contained value. When applicable, the position of the contained value can be controlled using a placeholder mechanism.
>> https://github.com/vt4a2h/ipa
>
> Seems like a bad idea to me.
> We have two different idioms for passing an invocable in C++:
> foo(&f, a, b, c); // (1)
> foo([&]() { f(a, b, c); }); // (2)
> Idiom (2) is more general-purpose: it allows `f` to be an overload set or a template, or to find `f` via ADL. Idiom (1) is less general and IMHO uglier, because it depends on the reader to realize that here function-invocation is represented by "f, x" instead of "f(x)". I like my function invocations to look like function invocations. (Although I can see how some people might think `[&]` is uglier than `,`, too!)
> So, why do we have idiom (1)? Only because some facilities, such as std::thread and std::async, were invented before we had discovered lambdas. (The C++0x cycle was very long, and not everything in C++11 came in at the same time.) So these facilities were designed around idiom (1) because that's the only idiom that existed at the time. Later, we got lambdas and realized that idiom (2) is all you need. Therefore, "post-modern" C++ facilities such as monadic optional/expected were designed around idiom (2), and idiom (1) became a footnote to history.
>
> At first glance, there's a consistency argument here: Plenty of facilities (std::invoke, std::async, std::thread) support both (1) and (2). Monadic optional/expected seem to support only (2). It would be more consistent if they'd, also, support both (1) and (2).
> But do monadic optional/expected support (2)? At second glance, I think not. They take a lambda with an argument. So really today we have the possibility of writing
> o.and_then([](int x) { return f(a, b, c, x); }); // OK
> o.and_then([](int x) { return f(x, a, b, c); }); // also OK
> Which of these would be implied by
> o.and_then(f, a, b, c); // fantasy syntax
> ? You arbitrarily pick one, but I don't think there's any logic to your choice.
>
> Meanwhile, you simultaneously propose a whole third way of doing it: `std::placeholders::value`. This would eliminate the arbitrariness above:
> o.and_then(f, a, b, c, std::placeholders::value); // fantasy syntax, OK
> o.and_then(f, std::placeholders::value, a, b, c); // fantasy syntax, also OK
>
> In fact it would be super-general in allowing you to write:
> o.and_then(f, std::placeholders::value, std::placeholders::value, std::placeholders::value); // fantasy syntax, calls f(x,x,x)
>
> o.and_then(f, std::placeholders::value, std::placeholders::value); // fantasy syntax, calls f(x,x)
>
> o.and_then(f, std::placeholders::value); // fantasy syntax, calls f(x)
> o.and_then(f); // fantasy syntax, calls f()... uh-oh! this is also real syntax that calls f(x)!
>
> The last line is a problem because `o.and_then(f)` already has a meaning. You can't change that! So, your proposed syntax is actually a generic-programming pitfall.
>
> Even if it weren't for that, the programmer might think it's weird that he can write `f(x, 42)` as `(f, std::placeholders::value, 42)`, but that there's no way to write `f(&x, 42)` or `f(x + 42)`. You could try to add one. At this point you're reinventing std::bind and all its wacky pitfalls. We invented lambdas in order to eliminate this mess of weird DSLs from C++. Don't bring it back!
>
> So you at least have to get rid of `std::placeholders::value`. Having done that, you're back to the problem that your choice of meaning for `o.and_then(f, a, b, c)` (prepend `x`, or append `x`?) is arbitrary. At the very least a real proposal would have to survey prospective users of this feature (I mean not a survey of humans' opinions, but a survey of real codebases that could use a mechanical translation of the existing `.and_then` into this style) and tally up how many of them would benefit from the prepend-`x` convention versus the append-`x` convention. At least then you'd be able to point to those numbers as justification for your choice.
>
> P.S., your Tony Tables aren't very "steel-manned." For example you have this on the left side of one of them:
>
> auto
>
> componentResult = extractContext()
> .and_then([&path, &metaData](Context context) {
>
> return
>
> createComponent
>
> (context, path, metaData);
> });
>
> But that's not equivalent to what's on the right (IIUC). It would be equivalent if it took `Context` by reference, not value.
> And it's not the kind of code I would tell anyone to write, either. You're spelling out captures as if the reader needs to beware of some pitfall. But this is the "99% case," where [&] Just Works and you shouldn't waste your reader's brainpower on parsing a capture-list. Let the compiler do the busywork for you!
>
> auto componentResult = extractContext()
> .and_then([&](const Context& context) {
> return createComponent(context, path, metaData);
> });
>
> This eliminates the first two bullet points in the following "Why not just use a lambda function?" Q&A: there is no long capture-list, and there is no long body (because the body is by definition just a single function call, possibly with a `return`). "Many named lambda functions can clutter a function" — lambdas don't have names. "A user has to write more (repeated) code" — example needed. Are you talking about the boilerplate of `[&]() { return`? (If not: should you have?)
>
> –Arthur
Thank you for the constructive feedback. I'll extend some examples and explanations based on it.
Lambda functions are a great tool, and we should use them. I have no doubts here. This proposal comes as additional functionality. It'll not help with function overloads (which we should probably avoid in general, but this is out of scope). It can be solved to a certain extent with the proposed placeholder, or simply by using lambda functions.
One of the major issues I see here is that lambda syntax can be too noisy for this specific scenario.
For example, it's correct that you can use "[&]" and let the compiler do the job, but there might be different code style preferences in a company and/or static analysis tooling setup. So in the worst-case scenario, it can turn into "[&a, &b, &c]" even if this is not necessary.
Same for the first argument; it's not always possible to use, say, "auto&& a". It can be a full type name for the sake of readability and a long argument name.
Reducing "[/*potentially long*/](/*potentially long*/) { return /*a function call of args*/; }" to "function name + args" is already not entirely bad in terms of readability. I use "potentially long" here because real names are usually longer than a single symbol.Also, code style might disallow one-line short lambdas. In this situation, even a chain of three "and_then"-like calls may look complicated.
To the "std::placeholders::value" topic, I proposed introducing some constraints: https://github.com/vt4a2h/ipa#add-new-constraints. I.e. you can pass zero or one of such objects, which I think is a sane restriction.
However, I didn't think about this case: "o.and_then(f, std::placeholders::value);". Well spotted! We can add one more restriction, like "if there is a single argument to forward, it cannot be a placeholder". In this case, it should be better in terms of assumptions about the meaning of "o.and_then(f)".
> Even if it weren't for that, the programmer might think it's weird that he can write `f(x, 42)` as `(f, std::placeholders::value, 42)`, but that there's no way to write `f(&x, 42)` or `f(x + 42)`. You could try to add one. At this point you're reinventing std::bind and all its wacky pitfalls. We invented lambdas in order toeliminatethis mess of weird DSLs from C++. Don't bring it back!
I don't think I fully get this point to be honest. Assume we have "expected<int, E>" and "f(int) -> expected<int, E>". With the new restriction from above this code will be ill-formed: "e.and_then(f, std::placeholders::value)". Ditto for "e.and_then(f, std::placeholders::value, 42)" (to many arguments to invoke "f").
A more interesting scenario is if we want to mutate a value and have a function like: "f(int&) -> expected<void, E>". It will not work in the existing proposal, because I used just "T". However, if I use something like: "using fwd_t = decltype(std::forward_like<Self>(std::declval<T>()));" (1), it'll be possible to invoke "e.and_then(f)". I'm not 100% sure whether this is something the API should allow, but it looks nice at a glance, and the behavior depends on the variable's category and the function parameters.
Now, assume we have a function with a different signature: "f(int, int&) -> expected<void, E>". Having (1) implemented, this code will work as expected: "e.and_then(f, 1, ipa::placeholders::value)". Same with "bind": "e.and_then(std::bind(f, 1, std::placeholders::_1))" or a lambda.
If we want to mutate not "e", but say, a variable "v", then we don't need to use placeholder "e.and_then(f, v)". For "bind", it looks more wordy: "e.and_then(std::bind_back(f, std::ref(v)))".
I'll add more examples to the repo.
Generally speaking, I'd like "ipa::placeholders::value" (or a similar abstraction) to have a clear meaning: "it'll be replaced with a contained value while keeping the value category". No other meanings or extra operations permitted.
> I mean not a survey of humans' opinions, but a survey of real codebases that could use a mechanical translation of the existing `.and_then` into this style
Good idea! Do you have any good open-source projects in mind that you may suggest exploring?
To Q&A, I think I answered everything. Here is the rest:
> lambdas don't have names.
Correct, my bad wording. I'll fix this. What I meant was:
auto a = [] () {};
auto b = [] () {};
auto c = [] () {};
e.and_then(a)
.and_then(b)
.and_then(c);
> "A user has to write more (repeated) code" — example needed. Are you talking about the boilerplate of `[&]() { return`? (If not:shouldyou have?)
Yes, this one and what I wrote in the first paragraphs of the answer. Will add more examples.
On Monday, July 6th, 2026 at 03:46, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]> wrote:
> On Sun, Jul 5, 2026 at 5:46 PM Vitaly Fanaskov via Std-Proposals <std-proposals_at_[hidden]> wrote:
>
>> This paper aims to extend monadic operations of std::expected and std::optional by allowing taking extra arguments. These arguments will be forwarded to the given function, passed after the first argument with the contained value. When applicable, the position of the contained value can be controlled using a placeholder mechanism.
>> https://github.com/vt4a2h/ipa
>
> Seems like a bad idea to me.
> We have two different idioms for passing an invocable in C++:
> foo(&f, a, b, c); // (1)
> foo([&]() { f(a, b, c); }); // (2)
> Idiom (2) is more general-purpose: it allows `f` to be an overload set or a template, or to find `f` via ADL. Idiom (1) is less general and IMHO uglier, because it depends on the reader to realize that here function-invocation is represented by "f, x" instead of "f(x)". I like my function invocations to look like function invocations. (Although I can see how some people might think `[&]` is uglier than `,`, too!)
> So, why do we have idiom (1)? Only because some facilities, such as std::thread and std::async, were invented before we had discovered lambdas. (The C++0x cycle was very long, and not everything in C++11 came in at the same time.) So these facilities were designed around idiom (1) because that's the only idiom that existed at the time. Later, we got lambdas and realized that idiom (2) is all you need. Therefore, "post-modern" C++ facilities such as monadic optional/expected were designed around idiom (2), and idiom (1) became a footnote to history.
>
> At first glance, there's a consistency argument here: Plenty of facilities (std::invoke, std::async, std::thread) support both (1) and (2). Monadic optional/expected seem to support only (2). It would be more consistent if they'd, also, support both (1) and (2).
> But do monadic optional/expected support (2)? At second glance, I think not. They take a lambda with an argument. So really today we have the possibility of writing
> o.and_then([](int x) { return f(a, b, c, x); }); // OK
> o.and_then([](int x) { return f(x, a, b, c); }); // also OK
> Which of these would be implied by
> o.and_then(f, a, b, c); // fantasy syntax
> ? You arbitrarily pick one, but I don't think there's any logic to your choice.
>
> Meanwhile, you simultaneously propose a whole third way of doing it: `std::placeholders::value`. This would eliminate the arbitrariness above:
> o.and_then(f, a, b, c, std::placeholders::value); // fantasy syntax, OK
> o.and_then(f, std::placeholders::value, a, b, c); // fantasy syntax, also OK
>
> In fact it would be super-general in allowing you to write:
> o.and_then(f, std::placeholders::value, std::placeholders::value, std::placeholders::value); // fantasy syntax, calls f(x,x,x)
>
> o.and_then(f, std::placeholders::value, std::placeholders::value); // fantasy syntax, calls f(x,x)
>
> o.and_then(f, std::placeholders::value); // fantasy syntax, calls f(x)
> o.and_then(f); // fantasy syntax, calls f()... uh-oh! this is also real syntax that calls f(x)!
>
> The last line is a problem because `o.and_then(f)` already has a meaning. You can't change that! So, your proposed syntax is actually a generic-programming pitfall.
>
> Even if it weren't for that, the programmer might think it's weird that he can write `f(x, 42)` as `(f, std::placeholders::value, 42)`, but that there's no way to write `f(&x, 42)` or `f(x + 42)`. You could try to add one. At this point you're reinventing std::bind and all its wacky pitfalls. We invented lambdas in order to eliminate this mess of weird DSLs from C++. Don't bring it back!
>
> So you at least have to get rid of `std::placeholders::value`. Having done that, you're back to the problem that your choice of meaning for `o.and_then(f, a, b, c)` (prepend `x`, or append `x`?) is arbitrary. At the very least a real proposal would have to survey prospective users of this feature (I mean not a survey of humans' opinions, but a survey of real codebases that could use a mechanical translation of the existing `.and_then` into this style) and tally up how many of them would benefit from the prepend-`x` convention versus the append-`x` convention. At least then you'd be able to point to those numbers as justification for your choice.
>
> P.S., your Tony Tables aren't very "steel-manned." For example you have this on the left side of one of them:
>
> auto
>
> componentResult = extractContext()
> .and_then([&path, &metaData](Context context) {
>
> return
>
> createComponent
>
> (context, path, metaData);
> });
>
> But that's not equivalent to what's on the right (IIUC). It would be equivalent if it took `Context` by reference, not value.
> And it's not the kind of code I would tell anyone to write, either. You're spelling out captures as if the reader needs to beware of some pitfall. But this is the "99% case," where [&] Just Works and you shouldn't waste your reader's brainpower on parsing a capture-list. Let the compiler do the busywork for you!
>
> auto componentResult = extractContext()
> .and_then([&](const Context& context) {
> return createComponent(context, path, metaData);
> });
>
> This eliminates the first two bullet points in the following "Why not just use a lambda function?" Q&A: there is no long capture-list, and there is no long body (because the body is by definition just a single function call, possibly with a `return`). "Many named lambda functions can clutter a function" — lambdas don't have names. "A user has to write more (repeated) code" — example needed. Are you talking about the boilerplate of `[&]() { return`? (If not: should you have?)
>
> –Arthur
Received on 2026-07-06 16:45:20
