Because if you didn't want checking, you'd be using a `union`. Also, you're not generally supposed to fetch values from a `variant`; you're supposed to be visiting it and acting on it that way.
--
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-proposals+unsubscribe@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/5dd620cd-a8ef-4354-a14c-6992421729ac%40isocpp.org.
-------- Original Message --------UTC Time: January 16, 2018 3:11 PMFrom: hodg...@gmail.com
Besides
that the same your argument can be applied for std::optional
too — "if
you didn't want checking, you'd be using a `T value`
instead of `std::optional<T> value`",
right? (But no, we still have checkless
`std::optional::operator*` — because there is no need to
check again in `if (o) {…; f(*o); …;}`-like cases, for
example.)
"you're
supposed to be visiting it and acting on it that way" — am
I supposed to act _only_ in that way?
Well, an object is often used in different ways from different parts of code: more carefree from relatively-rarely-called routines and more optimized from relatively-often-called routines. Therefore it's good for the same tool to support different modes; sometimes I want checking, sometimes I don't (i.e. I want std::variant that keeps its `index`, but sometimes to allow to do the `get`-like thing without internal checks); as for me, that's the main goal of C++ — to support higher-level programming without losing the low-level control. Absence of unchecked operations (comparable to std::optional::operator* and std::vector::operator[]) makes std::variant (almost) useless for me: it looks more psychologically-comfortable to use "enum state + union value" from the beginning, than to integrate std::variant with feeling that it implies checks that I can never get rid of (even in cases when inner type is guaranteed by outer logic).Besides that the same your argument can be applied for std::optional too — "if you didn't want checking, you'd be using a `T value` instead of `std::optional<T> value`", right? (But no, we still have checkless `std::optional::operator*` — because there is no need to check again in `if (o) {…; f(*o); …;}`-like cases, for example.)
"you're supposed to be visiting it and acting on it that way" — am I supposed to act _only_ in that way?
if(var.index() == required_index)
{
auto &value = get<required_index>(var);
}
if(auto ptr = get_if<required_index>(&var); ptr)
{
//use `ptr`
}
var[type_container<Type>{}];
var[3_const_index];
On Tuesday, January 16, 2018 at 10:53:38 AM UTC-5, Oleksandr Pikozh wrote:Well, an object is often used in different ways from different parts of code: more carefree from relatively-rarely-called routines and more optimized from relatively-often-called routines. Therefore it's good for the same tool to support different modes; sometimes I want checking, sometimes I don't (i.e. I want std::variant that keeps its `index`, but sometimes to allow to do the `get`-like thing without internal checks); as for me, that's the main goal of C++ — to support higher-level programming without losing the low-level control. Absence of unchecked operations (comparable to std::optional::operator* and std::vector::operator[]) makes std::variant (almost) useless for me: it looks more psychologically-comfortable to use "enum state + union value" from the beginning, than to integrate std::variant with feeling that it implies checks that I can never get rid of (even in cases when inner type is guaranteed by outer logic).Besides that the same your argument can be applied for std::optional too — "if you didn't want checking, you'd be using a `T value` instead of `std::optional<T> value`", right? (But no, we still have checkless `std::optional::operator*` — because there is no need to check again in `if (o) {…; f(*o); …;}`-like cases, for example.)
"you're supposed to be visiting it and acting on it that way" — am I supposed to act _only_ in that way?
I generally consider the use of `get` on a variant at all to be a code-smell. A function which takes a variant is a function that is expected to be able to operate correctly on all of the possible values in the variant. And the most effective way to achieve that is to use visitation when you want to access the member. By using `get`, you're saying that the code in question simply can't handle the other possible states of the variant. That's the wrong way to use a variant.
Visitation fixes so many problems. It makes it statically impossible for you to forget to handle a state. It makes your code more readable (to a degree), since you can use the variant value like it were a regular value. It discourages lots of conditions. And so forth.
That's not to say that `get` is bad. It's simply not a tool that should be in such frequent use that not having an unchecked version is a legitimate problem.
Consider your example of "when inner type is guaranteed by outer logic". How did this "outer logic" provide this guarantee? Did it create the `variant` with that state? If so... why did it bundle it in a `variant`, when it clearly only provides an object of a specific type? Did the "outer logic" already do the test? If so, why did it not unpack the object directly when it did that test, and just pass the object of the right type? If the "inner" logic can only handle a specific state, why is it being given an object that can assume states it cannot handle? That is, rather than:
if(var.index() == required_index)
{
auto &value = get<required_index>(var);
}
You do this:
if(auto ptr = get_if<required_index>(&var); ptr)
{
//use `ptr`
}
The fundamental difference between `optional` and `variant` is that the former only ever has 2 states. The behavior for the unengaged state will almost always be "do nothing", while the behavior for the engaged state will be "do something". Your code strongly suggests that you're trying to treat a `variant` like an `optional`. And we have a way to do that: get a pointer to the member.
Also, I'm curious as to your application where a single conditional check is sufficient to make the type "(almost) useless for me". Seriously, is that trivial performance really sufficient to go write your own variant type or to use something less feature-rich?
You're also missing another problem with checked vs. unchecked operations: consistency.
In the C++ standard library, the way you tell whether an operation is checked or unchecked is that the unchecked operation uses an operator. `vector::operator[]`, `optional::operator*`, and so on are operators. If you use a named function, then it is a checked operation. `vector::at`, `optional::value`, and so forth.
You can't really have an operator-form of the `variant` `get` function. After all, you need to provide a template index/type, and you can't pass something like that as a function parameter to an operator. After all, operators cannot take explicit template parameters without calling them by spelling out the operator (`variant::operator[]<std::string>(0)`).
On Tuesday, January 16, 2018 at 10:53:38 AM UTC-5, Oleksandr Pikozh wrote:Well, an object is often used in different ways from different parts of code: more carefree from relatively-rarely-called routines and more optimized from relatively-often-called routines. Therefore it's good for the same tool to support different modes; sometimes I want checking, sometimes I don't (i.e. I want std::variant that keeps its `index`, but sometimes to allow to do the `get`-like thing without internal checks); as for me, that's the main goal of C++ — to support higher-level programming without losing the low-level control. Absence of unchecked operations (comparable to std::optional::operator* and std::vector::operator[]) makes std::variant (almost) useless for me: it looks more psychologically-comfortable to use "enum state + union value" from the beginning, than to integrate std::variant with feeling that it implies checks that I can never get rid of (even in cases when inner type is guaranteed by outer logic).Besides that the same your argument can be applied for std::optional too — "if you didn't want checking, you'd be using a `T value` instead of `std::optional<T> value`", right? (But no, we still have checkless `std::optional::operator*` — because there is no need to check again in `if (o) {…; f(*o); …;}`-like cases, for example.)
"you're supposed to be visiting it and acting on it that way" — am I supposed to act _only_ in that way?
I generally consider the use of `get` on a variant at all to be a code-smell. A function which takes a variant is a function that is expected to be able to operate correctly on all of the possible values in the variant. And the most effective way to achieve that is to use visitation when you want to access the member. By using `get`, you're saying that the code in question simply can't handle the other possible states of the variant. That's the wrong way to use a variant.Visitation fixes so many problems. It makes it statically impossible for you to forget to handle a state. It makes your code more readable (to a degree), since you can use the variant value like it were a regular value. It discourages lots of conditions. And so forth.That's not to say that `get` is bad. It's simply not a tool that should be in such frequent use that not having an unchecked version is a legitimate problem.Consider your example of "when inner type is guaranteed by outer logic". How did this "outer logic" provide this guarantee? Did it create the `variant` with that state? If so... why did it bundle it in a `variant`, when it clearly only provides an object of a specific type? Did the "outer logic" already do the test? If so, why did it not unpack the object directly when it did that test, and just pass the object of the right type? If the "inner" logic can only handle a specific state, why is it being given an object that can assume states it cannot handle? That is, rather than:
if(var.index() == required_index)
{
auto &value = get<required_index>(var);
}You do this:
if(auto ptr = get_if<required_index>(&var); ptr)
{
//use `ptr`
}The fundamental difference between `optional` and `variant` is that the former only ever has 2 states. The behavior for the unengaged state will almost always be "do nothing", while the behavior for the engaged state will be "do something". Your code strongly suggests that you're trying to treat a `variant` like an `optional`. And we have a way to do that: get a pointer to the member.Also, I'm curious as to your application where a single conditional check is sufficient to make the type "(almost) useless for me". Seriously, is that trivial performance really sufficient to go write your own variant type or to use something less feature-rich?You're also missing another problem with checked vs. unchecked operations: consistency.In the C++ standard library, the way you tell whether an operation is checked or unchecked is that the unchecked operation uses an operator. `vector::operator[]`, `optional::operator*`, and so on are operators. If you use a named function, then it is a checked operation. `vector::at`, `optional::value`, and so forth.
In std::variant we have:
- ??? — doesn't check whether a variant contains specified index/type;
- method get — throws std::bad_variant_access when a variant doesn't contain specified index/type.
Why we don't have any fast (unchecked) ways to access std::variant? Assuming context when user has already checked for presence of required item somehow and now needs the fastest access (i.e. he agrees to get UB when absent).
I believe the standard committee could considered it if you have a motivating use case that cannot be managed with std::visit.
std::visit is a safe tool. Adding
an unsafe tool when a safe one could be used wouldn't help. We
need a use case that will be more efficient with your unsafe
function.
Vicente
On 16.01.18 18:35, Nicol Bolas wrote:
Looks like oversimplification for me.On Tuesday, January 16, 2018 at 10:53:38 AM UTC-5, Oleksandr Pikozh wrote:Well, an object is often used in different ways from different parts of code: more carefree from relatively-rarely-called routines and more optimized from relatively-often-called routines. Therefore it's good for the same tool to support different modes; sometimes I want checking, sometimes I don't (i.e. I want std::variant that keeps its `index`, but sometimes to allow to do the `get`-like thing without internal checks); as for me, that's the main goal of C++ — to support higher-level programming without losing the low-level control. Absence of unchecked operations (comparable to std::optional::operator* and std::vector::operator[]) makes std::variant (almost) useless for me: it looks more psychologically-comfortable to use "enum state + union value" from the beginning, than to integrate std::variant with feeling that it implies checks that I can never get rid of (even in cases when inner type is guaranteed by outer logic).Besides that the same your argument can be applied for std::optional too — "if you didn't want checking, you'd be using a `T value` instead of `std::optional<T> value`", right? (But no, we still have checkless `std::optional::operator*` — because there is no need to check again in `if (o) {…; f(*o); …;}`-like cases, for example.)
"you're supposed to be visiting it and acting on it that way" — am I supposed to act _only_ in that way?
I generally consider the use of `get` on a variant at all to be a code-smell. A function which takes a variant is a function that is expected to be able to operate correctly on all of the possible values in the variant. And the most effective way to achieve that is to use visitation when you want to access the member. By using `get`, you're saying that the code in question simply can't handle the other possible states of the variant. That's the wrong way to use a variant.
Visitation fixes so many problems. It makes it statically impossible for you to forget to handle a state. It makes your code more readable (to a degree), since you can use the variant value like it were a regular value. It discourages lots of conditions. And so forth.
That's not to say that `get` is bad. It's simply not a tool that should be in such frequent use that not having an unchecked version is a legitimate problem.
The set of real-life cases is much more rich.
E.g. we can have an std::variant field in a class — and some more-universal methods (that handle all states combinations) in the class can call some more-specialized ones (that handle only specific state combinations).
Of course, we can cache references (pointers) to members of std::variant and pass them from method to method (in addition to passing `this`, which already has access to the whole std::variant field), but it decreases aesthetics of the code (and additionally looks redundant at first glance, because with the most expected (but not guaranteed, of course) implementation of variant, the pointer to the member would have fixed offset from the pointer to the whole std::variant).
Yes, I consider variant as a generalization of optional.Consider your example of "when inner type is guaranteed by outer logic". How did this "outer logic" provide this guarantee? Did it create the `variant` with that state? If so... why did it bundle it in a `variant`, when it clearly only provides an object of a specific type? Did the "outer logic" already do the test? If so, why did it not unpack the object directly when it did that test, and just pass the object of the right type? If the "inner" logic can only handle a specific state, why is it being given an object that can assume states it cannot handle? That is, rather than:
if(var.index() == required_index)
{
auto &value = get<required_index>(var);
}
You do this:
if(auto ptr = get_if<required_index>(&var); ptr)
{
//use `ptr`
}
The fundamental difference between `optional` and `variant` is that the former only ever has 2 states. The behavior for the unengaged state will almost always be "do nothing", while the behavior for the engaged state will be "do something". Your code strongly suggests that you're trying to treat a `variant` like an `optional`. And we have a way to do that: get a pointer to the member.
But (for some reasons that look bogus for me) the generalization isn't smooth. IMHO it is better to fix it (even if no so much people feel practical problems due to that shagginess — just for aesthetic reasons at least).
It's more about consistency and psychology, not about actual benchmarking yet. I am writing right now a piece of code that with high probability will be a part of bottleneck. I realize that in some cases std::variant does redundant checks (at least with the way I use it). I expect them to be avoidable (as in std::optional and others), but they aren't. So it's psychologically simpler for me to throw std::variant out and not to use in the "foundation" (that will probably be a bottleneck) any things that have _known_ overhead.Also, I'm curious as to your application where a single conditional check is sufficient to make the type "(almost) useless for me". Seriously, is that trivial performance really sufficient to go write your own variant type or to use something less feature-rich?
if(auto var = get_variant(); var.index() == 3)
{
auto &value = get<3>(var);
}
Not necessarily variant::get will be a problem (and most probably something absolutely else will), but I don't like a feeling of creating future problems when writing the code (i.e. to write a code with a known and easy-avoidable redundancy, which doesn't brings and significant improvements into the code aesthetics or some other tremendous benefits).
I never heard about "methods-are-checked, functions-are-unchecked" rule in stdlib. Is is stated somewhere?You're also missing another problem with checked vs. unchecked operations: consistency.
In the C++ standard library, the way you tell whether an operation is checked or unchecked is that the unchecked operation uses an operator. `vector::operator[]`, `optional::operator*`, and so on are operators. If you use a named function, then it is a checked operation. `vector::at`, `optional::value`, and so forth.
You can't really have an operator-form of the `variant` `get` function. After all, you need to provide a template index/type, and you can't pass something like that as a function parameter to an operator. After all, operators cannot take explicit template parameters without calling them by spelling out the operator (`variant::operator[]<std::string>(0)`).
As for me, much more expected understanding of consistency here would be that every class (where it's applicable) has two ways of access: checked and unchecked. (If the std::vector and std::optional had no unchecked access, then I would have no disturbance about std::variant — I'd just see that as style of C++ stdlib; even if only std::optional had no unchecked access, then it would look understandable for me (stdlib is old, std::vector created long before std::optional and std::variant, style changed since then); but now it looks as breaking its own style.)
And if unchecked things really need to be operators (which I doubt, even if the such rule exists and wasn't broken yet, it will be surely be broken at some point of time in future due to the growing of stdlib and growing of the amount of strongly-needed-to-be-unchecked things), why not simply the explicit cast operator? (Ah, you are right, explicit cast operator will allow only access by type, not by index — still I don't feel both access by index and .)
Although I don't consider usage with duplicate types and (therefore) relying on index to be important parts of std::variant (personally I, if I designed std::variant, probably wouldn't allow it at all (possibly having it in a different class, like std::multiset vs std::set)), they are allowed, so the way is already chosen. And even I sometimes use it (e.g. when I got a set of having-common-interface-but-non-having-common-ancestor Java-iterator-like classes with `i.hasNext()` and `i.next()`-like methods and was going to build a `concatenate(i1, i2, i3)`-like function). But those who go this way (trying to assume that std::variant allows duplicate types) finally find incompleteness of std::variant for that approach.
P.S.: Sorry, if I described it not enough clearly, I was in hurry. Please, say if it's not understandable — then I may rewrite later.
Vicente
On Tue, Jan 16, 2018 at 3:37 PM, Vicente J. Botet Escriba <vicent...@wanadoo.fr> wrote:
Le 16/01/2018 à 16:38, Zhihao Yuan a écrit :
While it's not a bad idea (pattern matching in otherIt is weird. The committee required to remove from expected<T,E> every monadic operation. Now we have a paper that request the addition of the monadic operations to optional. Not that I'm against this member function approach, but we should be coherent.
languages naturally support this use), in practise
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0798r0.html
may be easier to use.
It may be that the committee wanted expected<> to get in first, then add monadic later. Like optional.Or it may just be that the committee is not one person, but a bunch, and it changes "its" mind regularly.
Then I didn't understood what the committee wanted. I believed that the committee wanted a non-member function interface, hence my Monadic paper.
--
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 view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/9fc3bfd4-dfa5-cc41-6e7a-f386e606094a%40wanadoo.fr.