With P2169 there is also now a much saner std::ignore that works in structured bindings:
auto [x, _, z] = f();
Which also leads to this fun semantical difference between the two:
_ = f(); // (1)
std::ignore = f(); // (2)
(2) will throw away the return value, whereas (1) will stay alive for the remainder of the current scope.
The slightly cursed reason is that for backwards compatibility, _ acts the same as a variable name, except with C++26 you can now redeclare as many _ as you want. But it’s actually really useful for RAII wrappers where you only care about the side effects, e.g.:
Oh, I need to put a pin in this! Recently there was an article complaining about an identical semantic difference in Rust, from the angle of “this isn’t spelled out in the spec”
It would be interesting to compare corresponding wordings from the Rust reference and C++ standard! Would the C++ docs be really more clear than the Rust ones?
Structured Binding As a Condition looks like it’ll finally make something like Swift’s if let possible in C++, which would make working with optionals a lot cleaner.
We’ve have if (optional<T> x = function_returning_optional()) as valid syntax since C++98. C++11 improved it with explicit operator bool and auto (nor more operator void* hacks). C++17 also adopted go-style if statements so you can now do if (auto x = function(); x->some_condition()) as well.
The difference here I think is that with if (auto x = function_returning_optional()) you have to go through either value, which is verbose, or operator* and operator->, which are error-prone (undefined behaviour on null optional). With if (auto [x] = function_returning_optional()) you could design an API without overloading operator* and operator->, while still retaining comfort of use; I’d say it’s even better since you can use plain old . to access members—an additional usage-site indication that the member access cannot fail.
With the * and -> operators, std::optional is essentially just documentation/to express intent compared to a pointer :/
The code base I $work on has a custom optional type, and the private: above the members was commented many years ago. So now there’s a lot of code that just accesses the value member directly… at least we tried.
The newer monadic APIs are a lot better: you can access the value only in a lambda, which does the validity checking.
You can probably create a layout-compatible subclass that makes the * and -> operators private and forces use of the monadic APIs with very little code.
Yeah lambdas seems like the simplest way to make it safe. But the deref operators still exist in std and are not deprecated which is unfortunate.
$work: there’s also politics and project management constraints that make fixing it not my job™. If it were a couple hours work I’d just do it but the error count caused by making the members private shut that hope down real quick.
x is still typed as optional so you have to use x.value() or *x
There’s nothing stopping you from using x in the else block, which either throws or is UB
PS: I wrote a lot of C++ between 2000 and 2017, and I am pretty sure you couldn’t declare a variable in an if statement in C++98; that was part of the “Go style” if from C++17.
PS: I wrote a lot of C++ between 2000 and 2017, and I am pretty sure you couldn’t declare a variable in an if statement in C++98; that was part of the “Go style” if from C++17.
You could but people were coming from C a lot where you can’t do it, and no one wanted to overload operator void* or operator bool so it wasn’t as common, but it’s how you would do “chaining” of dynamic_cast checks. I had to do this on a project in 2011 - 2012 as we moved from C++03 to C++11 and migrate us off of boost. (I’ve been writing C++ since about 2008, and professionally full time since 2011)
if (child* ptr = dynamic_cast<child*>(parent)) {
// we're a child* now
} else if (derived* ptr = dynamic_cast<derived*>(parent)) {
// we're a derived* now
}
// etc.
Contrast that with C where you could not do this sort of thing, and needed to do a double parentheses for assignment.
if ((ptr = (child*)parent))
Until C++11, the grammar had these “simple declarations” as type-specifier-seq declarator = assignment-expression and after C++11 it became attribute-specifier-seq(optional) decl-specifier-seq declarator brace-or-equal-initializer.
EDIT:
x is still typed as optional so you have to use x.value() or *x
Still typed, yes, which was pointed out in a separate reply, so I see the advantage I guess (though I’d not have to worry about .value() as that’s doing what the if assignment was for anyhow 😅)
With P2169 there is also now a much saner
std::ignorethat works in structured bindings:Which also leads to this fun semantical difference between the two:
(2) will throw away the return value, whereas (1) will stay alive for the remainder of the current scope.
The slightly cursed reason is that for backwards compatibility,
_acts the same as a variable name, except with C++26 you can now redeclare as many_as you want. But it’s actually really useful for RAII wrappers where you only care about the side effects, e.g.:Oh, I need to put a pin in this! Recently there was an article complaining about an identical semantic difference in Rust, from the angle of “this isn’t spelled out in the spec”
It would be interesting to compare corresponding wordings from the Rust reference and C++ standard! Would the C++ docs be really more clear than the Rust ones?
I would love to see a link to that Rust article!
For the C++ side, you can see the wording for the draft here.
https://lobste.rs/s/zmz8zv/rust_needs_official_specification
Seems that
_works like in Python.Structured Binding As a Condition looks like it’ll finally make something like Swift’s
if letpossible in C++, which would make working with optionals a lot cleaner.We’ve have
if (optional<T> x = function_returning_optional())as valid syntax since C++98. C++11 improved it withexplicit operator boolandauto(nor moreoperator void*hacks). C++17 also adopted go-styleifstatements so you can now doif (auto x = function(); x->some_condition())as well.The difference here I think is that with
if (auto x = function_returning_optional())you have to go through eithervalue, which is verbose, oroperator*andoperator->, which are error-prone (undefined behaviour on null optional). Withif (auto [x] = function_returning_optional())you could design an API without overloadingoperator*andoperator->, while still retaining comfort of use; I’d say it’s even better since you can use plain old.to access members—an additional usage-site indication that the member access cannot fail.Dang, Bjarne finally got his
operator .😔With the
*and->operators,std::optionalis essentially just documentation/to express intent compared to a pointer :/The code base I $work on has a custom optional type, and the
private:above the members was commented many years ago. So now there’s a lot of code that just accesses thevaluemember directly… at least we tried.The newer monadic APIs are a lot better: you can access the value only in a lambda, which does the validity checking.
You can probably create a layout-compatible subclass that makes the * and -> operators private and forces use of the monadic APIs with very little code.
Yeah lambdas seems like the simplest way to make it safe. But the deref operators still exist in std and are not deprecated which is unfortunate.
$work: there’s also politics and project management constraints that make fixing it not my job™. If it were a couple hours work I’d just do it but the error count caused by making the members private shut that hope down real quick.
I know. I dislike it because
*xelseblock, which either throws or is UBPS: I wrote a lot of C++ between 2000 and 2017, and I am pretty sure you couldn’t declare a variable in an
ifstatement in C++98; that was part of the “Go style” if from C++17.You could but people were coming from C a lot where you can’t do it, and no one wanted to overload
operator void*oroperator boolso it wasn’t as common, but it’s how you would do “chaining” ofdynamic_castchecks. I had to do this on a project in 2011 - 2012 as we moved from C++03 to C++11 and migrate us off of boost. (I’ve been writing C++ since about 2008, and professionally full time since 2011)Contrast that with C where you could not do this sort of thing, and needed to do a double parentheses for assignment.
Until C++11, the grammar had these “simple declarations” as type-specifier-seq declarator = assignment-expression and after C++11 it became attribute-specifier-seq(optional) decl-specifier-seq declarator brace-or-equal-initializer.
EDIT:
Still typed, yes, which was pointed out in a separate reply, so I see the advantage I guess (though I’d not have to worry about
.value()as that’s doing what theifassignment was for anyhow 😅)