Creating a string from a string_view is not cheap, so it should be explicit
| From: TONGARI J Sent: Tuesday, March 14, 2017 4:15 AM To: ISO C++ Standard - Discussion Reply To: std-dis...@isocpp.org Subject: [std-discussion] Why string_view->string is explicit? |
We don't have guidelines for when to be implicit or explicit. I'm working on that. https://github.com/tvaneerd/isocpp/blob/master/conversions.mdIn addition to 'not cheap', the constructor could throw. Throwing from 'unseen' code is (somewhat) harder to deal with.
I think if we had string_view earlier, then string from char * probably would have been explicit as well.Last reason (I need to add this to my paper somewhere) -we want to encourage string_view use, discourage string. Wherever you are doing that conversion, you should be thinking 'could I instead turn that string into a string_view?'
string_view sv(...);
string s = sv; // not allowed
s = sv; // allowedOn Tuesday, March 14, 2017 at 7:56:43 PM UTC+8, Tony VE wrote:We don't have guidelines for when to be implicit or explicit. I'm working on that. https://github.com/tvaneerd/isocpp/blob/master/conversions.md
In addition to 'not cheap', the constructor could throw. Throwing from 'unseen' code is (somewhat) harder to deal with.
Well, copy ctor may throw as well...I really don't see a point here. I don't think throw-ness or cheapness is a good criteria for explicit-ness.It's better to judge by semantic.
I think if we had string_view earlier, then string from char * probably would have been explicit as well.
Last reason (I need to add this to my paper somewhere) -we want to encourage string_view use, discourage string. Wherever you are doing that conversion, you should be thinking 'could I instead turn that string into a string_view?'
Frankly, I think a class that has a T::operator=(U) should have a corresponding implicit T(U). It's more symmetric and consistent.
On Tuesday, March 14, 2017 at 7:56:43 PM UTC+8, Tony VE wrote:We don't have guidelines for when to be implicit or explicit. I'm working on that. https://github.com/tvaneerd/isocpp/blob/master/conversions.mdIn addition to 'not cheap', the constructor could throw. Throwing from 'unseen' code is (somewhat) harder to deal with.Well, copy ctor may throw as well...I really don't see a point here. I don't think throw-ness or cheapness is a good criteria for explicit-ness.It's better to judge by semantic.
I think if we had string_view earlier, then string from char * probably would have been explicit as well.Last reason (I need to add this to my paper somewhere) -we want to encourage string_view use, discourage string. Wherever you are doing that conversion, you should be thinking 'could I instead turn that string into a string_view?'Frankly, I think a class that has a T::operator=(U) should have a corresponding implicit T(U). It's more symmetric and consistent.Look at what it is:string_view sv(...);
string s = sv; // not allowed
s = sv; // allowedA wat moment.
string s = sv; // not allowed
string s = sv;
to be "the same as" string s{sv};
string s = sv; On 14 March 2017 at 20:56, Vicente J. Botet Escriba
<vicent...@wanadoo.fr> wrote:
> I must to admit that this is the case and I agree with you that the current
> status is confusing. Many people say "Why
>
> string s = sv;
>
> is not explicit enough?"
There's existing code that relies on that not invoking an explicit
constructor, that's why.
struct Temp
{
Temp(float) {}
explicit Temp(int) {}
};
Temp t = 5;> However until we change this the operator= shouldn't be defined if the
> conversion is explicit.
Shouldn't be defined where? Anywhere? That seems like a quick
conclusion without rationale to back it up.
On Tuesday, March 14, 2017 at 3:00:13 PM UTC-4, Ville Voutilainen wrote:On 14 March 2017 at 20:56, Vicente J. Botet Escriba
<vicent...@wanadoo.fr> wrote:
> I must to admit that this is the case and I agree with you that the current
> status is confusing. Many people say "Why
>
> string s = sv;
>
> is not explicit enough?"
There's existing code that relies on that not invoking an explicit
constructor, that's why.
To go into further detail, consider the following:
struct Temp
{
Temp(float) {}
explicit Temp(int) {}
};
Temp t = 5;
That is currently valid code, but it will call the `float` version. If you change this to use the direct-initialization rules instead of copy-initialization rules, then it will call the `explicit` constructor, and therefore not be a backwards compatible change.
So to make this work backwards compatibly, you would have to create another initialization type, which is neither direct nor copy, but which works like copy-initialization save for the fact that if it fails to find a non-explicit constructor, it then looks for an explicit one.
And as much as I like the general idea behind this, I would much rather not add additional complications to initialization forms.
It should be noted that this is a difference between copy-initialization and copy-list-initialization. The former never considers `explicit` constructors, while the latter fails if an explicit constructor is selected. So `Temp t = {5};` is currently il-formed. And therefore, it would be a backwards compatible change to make `Temp t = {5};` into direct-list-initialization, since all ambiguous cases would have been il-formed.
> However until we change this the operator= shouldn't be defined if the
> conversion is explicit.
Shouldn't be defined where? Anywhere? That seems like a quick
conclusion without rationale to back it up.
The idea being that for any type `T`, and for any value `u`, if `T t = u` is valid, then `t = u` should also be valid. And if `t = u` is not valid, then `T t = u` should also not be valid.
To do otherwise is highly inconsistent. Though in this case, quite useful.
On Tue, Mar 14, 2017 at 10:17 AM, TONGARI J <tong...@gmail.com> wrote:On Tuesday, March 14, 2017 at 7:56:43 PM UTC+8, Tony VE wrote:We don't have guidelines for when to be implicit or explicit. I'm working on that. https://github.com/tvaneerd/isocpp/blob/master/conversions.mdIn addition to 'not cheap', the constructor could throw. Throwing from 'unseen' code is (somewhat) harder to deal with.Well, copy ctor may throw as well...I really don't see a point here. I don't think throw-ness or cheapness is a good criteria for explicit-ness.It's better to judge by semantic.By what semantic? That type two types represent the "same thing", or something else?
graphic::path path = graphic::ellipse(center, r);asio::io_service io;
asio::ip::tcp::socket sock(io);template <typename K>
T& map::operator[](K const& k);Converting a string to a string_view is not really a conversion, its more like constructing a reference to the data which performance wise is basically free (and possibly even more optimal then passing const string& because we don't have that extra indirection). The assignment is a shallow copy, similar to converting a child class pointer to a base class pointer. Converting from string_view to string however is a real data conversion. Memory has to be allocated and a copy of the data has to be made. In this case its better to have an explicit conversion so that people are prevented from easy to write hidden pessimizations. It marks in the code directly where copies of strings are being made.
void f(string);
f(s1); // `decltype(s1)` is `char const*`
f(s2); // `decltype(s2)` is `string`
f(s3); // `decltype(s3)` is `string_view`
void f(string const&);
void f(string_view);
We don't have guidelines for when to be implicit or explicit. I'm working on that. https://github.com/tvaneerd/isocpp/blob/master/conversions.md
In addition to 'not cheap', the constructor could throw. Throwing from 'unseen' code is (somewhat) harder to deal with.I think if we had string_view earlier, then string from char * probably would have been explicit as well.
Last reason (I need to add this to my paper somewhere) -we want to encourage string_view use, discourage string. Wherever you are doing that conversion, you should be thinking 'could I instead turn that string into a string_view?'
In my opinion, it the responsibility of the owner an object to be aware of when potentially expensive copies are being made. The distinction between implicit and explicit conversion should be made on the basis of safety and correctness, not performance. Disallowing conversion from `string_view` to `string` makes `string_view` awkward to use, which may discourage people from using it -- a net loss in my view. The usability of a type should not be hamstrung because of a fear that users might not aware of the performance costs of certain operations.
On Wednesday, March 22, 2017 at 3:56:07 AM UTC-4, joseph....@gmail.com wrote:In my opinion, it the responsibility of the owner an object to be aware of when potentially expensive copies are being made. The distinction between implicit and explicit conversion should be made on the basis of safety and correctness, not performance. Disallowing conversion from `string_view` to `string` makes `string_view` awkward to use, which may discourage people from using it -- a net loss in my view. The usability of a type should not be hamstrung because of a fear that users might not aware of the performance costs of certain operations.
A programmer who would genuinely give up using `string_view` in favor of `std::string` just because it doesn't implicitly convert to `std::string` is a programmer who doesn't deserve to have `string_view`. Providing an explicit conversion requires minimal effort on the caller's part; it is hardly sufficient reason to stop using a class.
And furthermore, if you have so many conversions from `string_view` to `std::string` that you encounter this problem frequently, you're using `string_view` wrong. You aren't supposed to frequently convert them to `std::string`s; they're for APIs where you don't need to modify the string.
People use C++ because they want performance. We should do what we can to make it hard for people to accidentally do slow things. The fast path should be the default wherever possible, and to discourage the slow path, we should
string s;
string_view sv = s;
string s1 = s; // okay
string s2 = sv; // errorvoid frobnicate(std::string const& s);frobnicate(std::string(sv))void frobnicate(std::string const&);
void frobnicate(std::string_view);
void frobnicate(char const*);On Wednesday, 22 March 2017 21:06:54 UTC+8, Nicol Bolas wrote:On Wednesday, March 22, 2017 at 3:56:07 AM UTC-4, joseph....@gmail.com wrote:In my opinion, it the responsibility of the owner an object to be aware of when potentially expensive copies are being made. The distinction between implicit and explicit conversion should be made on the basis of safety and correctness, not performance. Disallowing conversion from `string_view` to `string` makes `string_view` awkward to use, which may discourage people from using it -- a net loss in my view. The usability of a type should not be hamstrung because of a fear that users might not aware of the performance costs of certain operations.
A programmer who would genuinely give up using `string_view` in favor of `std::string` just because it doesn't implicitly convert to `std::string` is a programmer who doesn't deserve to have `string_view`. Providing an explicit conversion requires minimal effort on the caller's part; it is hardly sufficient reason to stop using a class.
This is typical "expert" mentality. Whether your users are the general public or other programmers, if your product violates their expectations, they will be reluctant to use it. Ignore your users at your own peril.
And furthermore, if you have so many conversions from `string_view` to `std::string` that you encounter this problem frequently, you're using `string_view` wrong. You aren't supposed to frequently convert them to `std::string`s; they're for APIs where you don't need to modify the string.
Or maybe the user simply has requirements that do not align with your expectations. I don't presume to know user requirements.
People use C++ because they want performance. We should do what we can to make it hard for people to accidentally do slow things. The fast path should be the default wherever possible, and to discourage the slow path, we should
Performance is just one concern of many.
Disallowing conversion from `string_view` to `string` is not making the fast path the default; it is disabling the default path because it has been deemed too slow.
I have no idea why this conversion has been singled out in this regard; consider the following:string s;
string_view sv = s;
string s1 = s; // okay
string s2 = sv; // error
These operations do the same exact thing.
Why is one allowed and the other isn't? This is inconsistent. It makes no sense.
As another example of how requiring explicit conversion in this case is wrong, consider a programmer who is using a 3rd party library with the following function:void frobnicate(std::string const& s);
The programmer decides to start using `string_view` throughout his code base. Because `string_view` doesn't convert to `string`, wherever he calls `frobnicate`, he has to add an explicit conversion to `string`:frobnicate(std::string(sv))
Unfortunately, this does have an impact on the performance of the user's code. However, this cannot be helped because the 3rd party library cannot be modified.
At some point in the future, the programmer switches to a new version of the 3rd party library, which has updated its interfaces to support `string_view`. To avoid breaking user code, extra overloads of the `frobnicate` function have been added:
void frobnicate(std::string const&);
void frobnicate(std::string_view);
void frobnicate(char const*);
Unfortunately, because the programmer is casting each `string_view` to a `string` wherever he calls `frobnicate`, he experiences no performance increase. If conversion from `string_view` to `string` had been supported, this would not have been the case, and the performance of the programmer's code would have returned to its pre-`string_view` state.
The problem here is that the explicit cast to `string` has suppressed the type system, which has disabled the overload resolution mechanism which would otherwise have chosen the most appropriate function to call. As I said previously, if a conversion is correct and safe, it should be implicit. Performance should not be a consideration.
On Tuesday, 14 March 2017 19:56:43 UTC+8, Tony VE wrote:We don't have guidelines for when to be implicit or explicit. I'm working on that. https://github.com/tvaneerd/isocpp/blob/master/conversions.md
In addition to 'not cheap', the constructor could throw. Throwing from 'unseen' code is (somewhat) harder to deal with.
I think if we had string_view earlier, then string from char * probably would have been explicit as well.
I believe that the only criteria that should be used to choose between implicit and explicit conversion are correctness (being the same "platonic" thing) and safety (e.g. narrowing conversions are not safe). Performance and the possibility of exceptions should not be of any concern.
void f(string);
void f(string
const&);
void f(string_view);
Regarding performance, if conversion from A -> B is correct and safe, it should be viewed in the same way as a copy operation of A -> A. We don't make copies explicit if they breach some "performance impact" threshold. And how would we determine such a threshold anyway? Different users have different performance requirements.
Regarding exceptions, exceptions occur in exceptional circumstances, and should not be expected during regular program operation. As such, exceptions are generally dealt with somewhere high up the call stack, not at the point at which they are thrown. And anyway, I am skeptical of the premise that if the programmer is forced to write `std::string(sv)` then they will automatically know that this conversion can throw. Especially with an exception as rare as `std::bad_alloc`, the programmer is unlikely to know that it might be thrown, and even if they do, they are unlikely to care (unless they are working on a platform with severely restricted memory resources, in which case they will be catching the exception somewhere up the call stack as I mentioned).
Explicit conversions represent a suppression of the type system. They are the programmer telling the compiler, "Look, I know this doesn't seem safe, but trust me, I've got this". An explicit conversion like `std::string(sv)` is the same as `static_cast<std::string>(sv)`. Forcing users to explicitly convert `string_view` to `string` is forcing them to suppress the safety provided by the type system, despite the fact that the conversion is actually correct and safe! If at some point in the future the type of `sv` changes such that the conversion is no longer correct or safe, they will not be warned about it by the compiler.
Last reason (I need to add this to my paper somewhere) -we want to encourage string_view use, discourage string. Wherever you are doing that conversion, you should be thinking 'could I instead turn that string into a string_view?'
Making `string_view` awkward to use will discourage its use IMO.
This view would change when people adapt its string const& to string_view. This could take time, but I believe it is the way to go.
On Wednesday, March 22, 2017 at 9:45:57 PM UTC-4, joseph....@gmail.com wrote:On Wednesday, 22 March 2017 21:06:54 UTC+8, Nicol Bolas wrote:On Wednesday, March 22, 2017 at 3:56:07 AM UTC-4, joseph....@gmail.com wrote:In my opinion, it the responsibility of the owner an object to be aware of when potentially expensive copies are being made. The distinction between implicit and explicit conversion should be made on the basis of safety and correctness, not performance. Disallowing conversion from `string_view` to `string` makes `string_view` awkward to use, which may discourage people from using it -- a net loss in my view. The usability of a type should not be hamstrung because of a fear that users might not aware of the performance costs of certain operations.
A programmer who would genuinely give up using `string_view` in favor of `std::string` just because it doesn't implicitly convert to `std::string` is a programmer who doesn't deserve to have `string_view`. Providing an explicit conversion requires minimal effort on the caller's part; it is hardly sufficient reason to stop using a class.
This is typical "expert" mentality. Whether your users are the general public or other programmers, if your product violates their expectations, they will be reluctant to use it. Ignore your users at your own peril.And furthermore, if you have so many conversions from `string_view` to `std::string` that you encounter this problem frequently, you're using `string_view` wrong. You aren't supposed to frequently convert them to `std::string`s; they're for APIs where you don't need to modify the string.
Or maybe the user simply has requirements that do not align with your expectations. I don't presume to know user requirements.
If that's the case, how can you claim that not having this implicit constructor "violates their expectations". It violates your expectations, but maybe users have requirements that do not align with your expectations.
People use C++ because they want performance. We should do what we can to make it hard for people to accidentally do slow things. The fast path should be the default wherever possible, and to discourage the slow path, we should
Performance is just one concern of many.
Yes, it is. But that's no excuse for dismissing it.Disallowing conversion from `string_view` to `string` is not making the fast path the default; it is disabling the default path because it has been deemed too slow.
That is too much like assuming your own conclusion. By declaring the conversion from `string_view` to `std::string` to be "the default path", you're starting from the assumption that the conversion ought to be implicit, then creating a framework that says that being explicit is wrong.
I have no idea why this conversion has been singled out in this regard; consider the following:string s;
string_view sv = s;
string s1 = s; // okay
string s2 = sv; // error
These operations do the same exact thing.
One converts one object to another type. The other copies an object. They only do "the same exact thing" on a conceptual level (copying a string); at a language level, they're quite different. If the user wanted to live at the conceptual level, they wouldn't have created a `string_view` at all.
A `string_view` is not a string; it is a view of a string; it's a range with features. And a range is not a container. There is no implicit conversion from ranges to containers (well technically, the iterator pair constructors are not marked `explicit`), and nor should there be. So if ranges don't have implicit conversions to containers, why should `string_view` have implicit conversions to `string`?
You can consider this all to be sophistry to some users. And that may well be the case. But the distinction undeniably exists. So who is to say that the users who ignore the distinction are more correct than those who recognize it?
Why is one allowed and the other isn't? This is inconsistent. It makes no sense.
As another example of how requiring explicit conversion in this case is wrong, consider a programmer who is using a 3rd party library with the following function:void frobnicate(std::string const& s);
The programmer decides to start using `string_view` throughout his code base. Because `string_view` doesn't convert to `string`, wherever he calls `frobnicate`, he has to add an explicit conversion to `string`:frobnicate(std::string(sv))
Unfortunately, this does have an impact on the performance of the user's code. However, this cannot be helped because the 3rd party library cannot be modified.
To get the full benefits from types like `string_view`, you have to use them everywhere. Which means that, if you have to talk to libraries that don't use `string_view`, then you are probably not going to be better off by using it.
So "cannot be helped" is a strong statement. It can be helped (depending on where `sv` comes from); you may simply need to reconsider your desire to use `string_view`. I've encountered such problems in the past, where I tried to switch to `string_view`, but realized that I would have to copy more than if I just used `std::string`, thanks to frequent use of some APIs that used `string`.
Also, the writer of this library should have [[deprecated]] the `const string&` overload, which would give users a warning when they use it. That should be done regardless of what `string_view` does. After all, if I'm using a home-grown string type, it's much more performance-friendly for me to create a `string_view` than to copy to a `std::string`. So I'd want a warning to happen once the performance-friendly API is available.
So it seems to me that this problem would be sorted out soon enough.
The problem here is that the explicit cast to `string` has suppressed the type system, which has disabled the overload resolution mechanism which would otherwise have chosen the most appropriate function to call. As I said previously, if a conversion is correct and safe, it should be implicit. Performance should not be a consideration.
... huh? How can you say performance shouldn't be a consideration when you just showed how not having this implicit conversion could negatively impact performance?
Le 22/03/2017 à 10:01, joseph....@gmail.com a écrit :
I think that the performance and the exception safety criteria should be compared to the ones of the ToType copy constructor.On Tuesday, 14 March 2017 19:56:43 UTC+8, Tony VE wrote:We don't have guidelines for when to be implicit or explicit. I'm working on that. https://github.com/tvaneerd/isocpp/blob/master/conversions.md
In addition to 'not cheap', the constructor could throw. Throwing from 'unseen' code is (somewhat) harder to deal with.
I think if we had string_view earlier, then string from char * probably would have been explicit as well.
I believe that the only criteria that should be used to choose between implicit and explicit conversion are correctness (being the same "platonic" thing) and safety (e.g. narrowing conversions are not safe). Performance and the possibility of exceptions should not be of any concern.
In this case they have the same performance and exception safety.
If we have this signature
void f(string);
calling with string, string_view or const char* has almost the same performances and exceptions safety.
However C++ is not only a copy semantic language.
void f(string const&);
When you have a function that takes a parameter by lvalue reference, we can avoid the copy when we have already a string.
But it is not the same for string_view or const char*. This is why in those cases we need to add a new overload
void f(string_view);
Once you have this overload you don't need anymore the implicit conversion. Don't providing is forcing you to add this overload that is more efficient and exception-safe.
If the language made the difference between a conversion from U to T because we need a T and a conversion from U to T because we need a T const& one could be implicit and the other explicit.
This is the problem. We cannot.
Regarding performance, if conversion from A -> B is correct and safe, it should be viewed in the same way as a copy operation of A -> A. We don't make copies explicit if they breach some "performance impact" threshold. And how would we determine such a threshold anyway? Different users have different performance requirements.
Compilers can optimize code in case the code is noexcept. Using a function that takes string_view instead will help.
Regarding exceptions, exceptions occur in exceptional circumstances, and should not be expected during regular program operation. As such, exceptions are generally dealt with somewhere high up the call stack, not at the point at which they are thrown. And anyway, I am skeptical of the premise that if the programmer is forced to write `std::string(sv)` then they will automatically know that this conversion can throw. Especially with an exception as rare as `std::bad_alloc`, the programmer is unlikely to know that it might be thrown, and even if they do, they are unlikely to care (unless they are working on a platform with severely restricted memory resources, in which case they will be catching the exception somewhere up the call stack as I mentioned).
I understand your concerns, and maybe we need an additional Friendly criteria. However in this particular case I wouldn't use an implicit conversion, as the goal of string_view was, just that, perform better and without exceptions when we need a string const.
Explicit conversions represent a suppression of the type system. They are the programmer telling the compiler, "Look, I know this doesn't seem safe, but trust me, I've got this". An explicit conversion like `std::string(sv)` is the same as `static_cast<std::string>(sv)`. Forcing users to explicitly convert `string_view` to `string` is forcing them to suppress the safety provided by the type system, despite the fact that the conversion is actually correct and safe! If at some point in the future the type of `sv` changes such that the conversion is no longer correct or safe, they will not be warned about it by the compiler.
This view would change when people adapt its string const& to string_view. This could take time, but I believe it is the way to go.
Last reason (I need to add this to my paper somewhere) -we want to encourage string_view use, discourage string. Wherever you are doing that conversion, you should be thinking 'could I instead turn that string into a string_view?'
Making `string_view` awkward to use will discourage its use IMO.
Best,
Vicente
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.
> The programmer decides to start using `string_view` throughout his code base. Because `string_view` doesn't convert to `string`, wherever he calls `frobnicate`, he has to add an explicit conversion to `string`I would have to say "good" at this point. My reasoning is as follows:There seems to me to be a logical equivalence when creating a string_view from a string. I.e. the string_view is a view of the string.Creating a string from a string_view does not infer the same equivalence - I think the correct term is that the operation is not associative. A.B is not the same as B.A. This conversion actually constructs a new copy, not a view or reference. It is a different kind of operation.
In my view, statement of intent of this copy should be explicit, as it has side-effects. (i.e. the two objects are now unrelated).
--
---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-discussion/YVGIEJOt_E0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-discussion+unsubscribe@isocpp.org.
On Thursday, 23 March 2017 14:02:04 UTC+8, Nicol Bolas wrote:On Wednesday, March 22, 2017 at 9:45:57 PM UTC-4, joseph....@gmail.com wrote:On Wednesday, 22 March 2017 21:06:54 UTC+8, Nicol Bolas wrote:On Wednesday, March 22, 2017 at 3:56:07 AM UTC-4, joseph....@gmail.com wrote:In my opinion, it the responsibility of the owner an object to be aware of when potentially expensive copies are being made. The distinction between implicit and explicit conversion should be made on the basis of safety and correctness, not performance. Disallowing conversion from `string_view` to `string` makes `string_view` awkward to use, which may discourage people from using it -- a net loss in my view. The usability of a type should not be hamstrung because of a fear that users might not aware of the performance costs of certain operations.
A programmer who would genuinely give up using `string_view` in favor of `std::string` just because it doesn't implicitly convert to `std::string` is a programmer who doesn't deserve to have `string_view`. Providing an explicit conversion requires minimal effort on the caller's part; it is hardly sufficient reason to stop using a class.
This is typical "expert" mentality. Whether your users are the general public or other programmers, if your product violates their expectations, they will be reluctant to use it. Ignore your users at your own peril.And furthermore, if you have so many conversions from `string_view` to `std::string` that you encounter this problem frequently, you're using `string_view` wrong. You aren't supposed to frequently convert them to `std::string`s; they're for APIs where you don't need to modify the string.
Or maybe the user simply has requirements that do not align with your expectations. I don't presume to know user requirements.
If that's the case, how can you claim that not having this implicit constructor "violates their expectations". It violates your expectations, but maybe users have requirements that do not align with your expectations.
A `string_view` is like a more flexible `string const&`. They are conceptually the same thing. It is natural to expect `string_view` to convert to `string`, just as `string const&` does. The feature has been disabled because it has been decided that if someone has a `string_view`, then they probably want to avoid making deep copies. I'm unwilling to make this assumption.
People use C++ because they want performance. We should do what we can to make it hard for people to accidentally do slow things. The fast path should be the default wherever possible, and to discourage the slow path, we should
Performance is just one concern of many.
Yes, it is. But that's no excuse for dismissing it.Disallowing conversion from `string_view` to `string` is not making the fast path the default; it is disabling the default path because it has been deemed too slow.
That is too much like assuming your own conclusion. By declaring the conversion from `string_view` to `std::string` to be "the default path", you're starting from the assumption that the conversion ought to be implicit, then creating a framework that says that being explicit is wrong.
What is the default path for changing one two to another if not a conversion? Conversion from `string_view` to `string` inherently involves a deep copy. We cannot make it any faster; we can only disable it altogether.
On Thursday, March 23, 2017 at 4:30:42 AM UTC-4, Joseph Thomson wrote:On Thursday, 23 March 2017 14:02:04 UTC+8, Nicol Bolas wrote:On Wednesday, March 22, 2017 at 9:45:57 PM UTC-4, joseph....@gmail.com wrote:On Wednesday, 22 March 2017 21:06:54 UTC+8, Nicol Bolas wrote:On Wednesday, March 22, 2017 at 3:56:07 AM UTC-4, joseph....@gmail.com wrote:In my opinion, it the responsibility of the owner an object to be aware of when potentially expensive copies are being made. The distinction between implicit and explicit conversion should be made on the basis of safety and correctness, not performance. Disallowing conversion from `string_view` to `string` makes `string_view` awkward to use, which may discourage people from using it -- a net loss in my view. The usability of a type should not be hamstrung because of a fear that users might not aware of the performance costs of certain operations.
A programmer who would genuinely give up using `string_view` in favor of `std::string` just because it doesn't implicitly convert to `std::string` is a programmer who doesn't deserve to have `string_view`. Providing an explicit conversion requires minimal effort on the caller's part; it is hardly sufficient reason to stop using a class.
This is typical "expert" mentality. Whether your users are the general public or other programmers, if your product violates their expectations, they will be reluctant to use it. Ignore your users at your own peril.And furthermore, if you have so many conversions from `string_view` to `std::string` that you encounter this problem frequently, you're using `string_view` wrong. You aren't supposed to frequently convert them to `std::string`s; they're for APIs where you don't need to modify the string.
Or maybe the user simply has requirements that do not align with your expectations. I don't presume to know user requirements.
If that's the case, how can you claim that not having this implicit constructor "violates their expectations". It violates your expectations, but maybe users have requirements that do not align with your expectations.
A `string_view` is like a more flexible `string const&`. They are conceptually the same thing. It is natural to expect `string_view` to convert to `string`, just as `string const&` does. The feature has been disabled because it has been decided that if someone has a `string_view`, then they probably want to avoid making deep copies. I'm unwilling to make this assumption.
No, the implicit conversion is disabled because your initial perspective on `string_view` is incorrect. `string_view` is not "a more flexible `string const &`. It is a range of a string. Yes, it has string-specific interfaces, but that doesn't change the fact that it is not a container. It's just a range.
And ranges are not implicitly convertible to containers.
People use C++ because they want performance. We should do what we can to make it hard for people to accidentally do slow things. The fast path should be the default wherever possible, and to discourage the slow path, we should
Performance is just one concern of many.
Yes, it is. But that's no excuse for dismissing it.Disallowing conversion from `string_view` to `string` is not making the fast path the default; it is disabling the default path because it has been deemed too slow.
That is too much like assuming your own conclusion. By declaring the conversion from `string_view` to `std::string` to be "the default path", you're starting from the assumption that the conversion ought to be implicit, then creating a framework that says that being explicit is wrong.
What is the default path for changing one two to another if not a conversion? Conversion from `string_view` to `string` inherently involves a deep copy. We cannot make it any faster; we can only disable it altogether.
If "the default path" is conversion, then why is making conversion explicit "disabling the default path"? An explicit conversion is still a conversion; you just have to ask for it.
I don't know why you insist on calling explicit conversions "disabled". "Disabled" would mean "you can't do it," not "it requires more text".
> You are talking about commutativityThank you for the correction.> In our case, `string_view` and `string` are both conceptually "strings"
I respectfully disagree.
A new string_view is more similar in concept to a std::reference_wrapper<std::string> than it is to a std::string.
Copying it into another std::string_view creates two views of the same string. Almost exactly like a pointer, but better. It is a pointer with guarantees and behavioural constraints.
argument 1:A new std::string is a brand new object which bears no relation to its source. Modifying the source does not invalidate the new string. They are completely separated. Modifying the source of a string_view invalidates the string_view. It seems to me that one ought to be very sure of which of these will happen when writing code.
argument 2:Allowing std::string to be created from a string_view seems to me to be equivalent to providing std::unique_ptr with a constructor that takes (std::observer_ptr<T> observer), which then executes the following code:unique_ptr(new T(*observer))i.e. automatically dereferencing the pointer and calling a copy constructor on the dereferenced value.I think the c++ community would find this behaviour somewhat "surprising".
You want an implicit conversion from string_view to string. Do you also want 3rd-party string types to be implicitly constructable from string_view?
#include<vector>
template<typename T>
struct Implicit_Impl
{
T t; //probably "&" would be best there
template<typename TT>
operator TT()
{
return TT(std::move(t));
}
};
template<typename T>
Implicit_Impl<T> implicit_cast(T&& t)
{
return { std::forward<T>(t) };
}
std::vector<int> i = { 5.5, 5.7, }; //error!
std::vector<int> j = { implicit_cast(5.5), implicit_cast(5.7), }; //ok
On 24 Mar 2017 12:16 am, "Richard Hodges" <hodg...@gmail.com> wrote:
> You are talking about commutativity
Thank you for the correction.
> In our case, `string_view` and `string` are both conceptually "strings"
I respectfully disagree.
I respectfully disagree too :)
A new string_view is more similar in concept to a std::reference_wrapper<std::string> than it is to a std::string.
I have to point out, `reference_wrapper<string>` is convertible to `string`.
static_assert(is_convertible_v<reference_wrapper<string>, string>);--
(@Joseph, can you please configure your mailer to quote properly? There
is zero distinction between your text and text to which you are replying...)
> I respectfully disagree too :)It seems we are at an ideological impasse.I take on board your comments about the behaviour of construction from references. They do muddy the waters of my argument.I think I can summaries our divergent opinions in the following way - you value convenience, and have the view that the wider community will too, while I value the necessity of explicit expressions of logical intent.
auto it = m.find(sv); // okay, because `string` converts to `string_view`m[sv] = v; // error: no `operator[](std::string_view)`m[std::string(sv)] = v; // `string` constructed even if entry existstemplate <typename K, typename = enable_if_t<is_convertible_v<K, key_type>>>
T& map::operator[](K const& key);auto it = m.lower_bound(sv);
if (it == m.end() || std::less<>()(sv, it->first)) {
it = m.emplace_hint(it, sv, v);
} else {
it->second = v;
}On Friday, 24 March 2017 01:27:46 UTC+8, Richard Hodges wrote:> I respectfully disagree too :)It seems we are at an ideological impasse.I take on board your comments about the behaviour of construction from references. They do muddy the waters of my argument.I think I can summaries our divergent opinions in the following way - you value convenience, and have the view that the wider community will too, while I value the necessity of explicit expressions of logical intent.
I value convenience, but I value correctness and consistency more. It just happens that convenience often results from applying correct and consistent design principles. Efficiency also tends to result from applying correct and consistent design principles. The concern about efficiency of conversion from `string_view` to `string` is misplaced. I have already demonstrated how requiring explicit invocation of the `string` constructor encourages suppression of the type system and interferes with overload resolution, resulting in sub-optimal performance if the programmer is not very careful. But this is by no means the biggest problem...
To demonstrate how the this decision to disable the conversion can go way beyond the inconvenience (and potential inadvertent performance impact) of having to write `std::string(sv)` every now and then, let me re-state the problem that originally inspired me to think about this. Consider using a `string_view` as a key in a `map<string, X, less<>>`. Heterogeneous keys are already supported for observers:auto it = m.find(sv); // okay, because `string` converts to `string_view`
But heterogeneous keys are not supported for modifiers:m[sv] = v; // error: no `operator[](std::string_view)`
In this case, converting to `string` can be very inefficient, especially inside a loop:m[std::string(sv)] = v; // `string` constructed even if entry exists
We could modify `map` to support heterogeneous keys in this case with the requirement that the key type be convertible to `map::key_type`:template <typename K, typename = enable_if_t<is_convertible_v<K, key_type>>>
T& map::operator[](K const& key);
But we still cannot use `string_view` because it is not convertible to `string`. Instead, we are forced to essentially re-implement `operator[]` just to get optimal efficiency:
On Thursday, March 23, 2017 at 10:14:08 PM UTC-4, joseph....@gmail.com wrote:On Friday, 24 March 2017 01:27:46 UTC+8, Richard Hodges wrote:> I respectfully disagree too :)It seems we are at an ideological impasse.I take on board your comments about the behaviour of construction from references. They do muddy the waters of my argument.I think I can summaries our divergent opinions in the following way - you value convenience, and have the view that the wider community will too, while I value the necessity of explicit expressions of logical intent.
I value convenience, but I value correctness and consistency more. It just happens that convenience often results from applying correct and consistent design principles. Efficiency also tends to result from applying correct and consistent design principles. The concern about efficiency of conversion from `string_view` to `string` is misplaced. I have already demonstrated how requiring explicit invocation of the `string` constructor encourages suppression of the type system and interferes with overload resolution, resulting in sub-optimal performance if the programmer is not very careful. But this is by no means the biggest problem...
To demonstrate how the this decision to disable the conversion can go way beyond the inconvenience (and potential inadvertent performance impact) of having to write `std::string(sv)` every now and then, let me re-state the problem that originally inspired me to think about this. Consider using a `string_view` as a key in a `map<string, X, less<>>`. Heterogeneous keys are already supported for observers:auto it = m.find(sv); // okay, because `string` converts to `string_view`
But heterogeneous keys are not supported for modifiers:m[sv] = v; // error: no `operator[](std::string_view)`
In this case, converting to `string` can be very inefficient, especially inside a loop:m[std::string(sv)] = v; // `string` constructed even if entry exists
We could modify `map` to support heterogeneous keys in this case with the requirement that the key type be convertible to `map::key_type`:template <typename K, typename = enable_if_t<is_convertible_v<K, key_type>>>
T& map::operator[](K const& key);
But we still cannot use `string_view` because it is not convertible to `string`. Instead, we are forced to essentially re-implement `operator[]` just to get optimal efficiency:
Or you could choose `is_constructible` rather than `is_convertible`. After all, the user's clear intent is to use `K` to create a `key_type` if that is necessary. The user would otherwise have done `map_obj[key_type(k)]` to access the value. You're simply providing a potentially more optimal form of that.
safer<long> a = 100000;
safer<short> b = a; // error (narrowing conversion)
map<safer<short>, string> m;
m[a] = "hello, world"; // okay (oops)The question you have for heterogeneous maps is this: is it reasonable to assume that two types which are comparable are also implicitly convertible? I think there's a decent argument to be made either way.
Or even better, since this feature can't be used unless the comparison has the heterogeneous flag, you could also allow the comparison object itself to do the conversion from `K` to `key_type`. That allows the comparison object to handle various cases where you don't want implicit conversions, but you do want conversions for this particular use of `map`.
string_view sv;
string s = sv; // error
s = sv; // a-okay...2017-03-24 13:47 GMT+01:00 <joseph....@gmail.com>:
> I've just realised that assignment of `string_view` to `string` is
> defined... what??
>
> string_view sv;
> string s = sv; // error
> s = sv; // a-okay...
>
> If conversion from `string_view` to `string` is disabled because it
> shouldn't be possible to implicitly perform deep copies of `string_view`,
> then why on earth is assignment of `string_view` to `string` allowed?! This
> is totally inconsistent.
I disagree that this implies that the design is "totally"
inconsistent, construction is a different thing than assignment. In
particular, assignability would never be considered during
construction (But vice versa), so the result of std::is_convertible is
never weakened by the existence of a assignment operator.
This is
similar for std::regex: There is an *explicit* constructor from char
pointer (and one from basic_string):
explicit basic_regex(const charT* p, flag_type f = regex_constants::ECMAScript);
but there is also an assignment operator from char pointer (and one
from basic_string):
basic_regex& operator=(const charT* ptr);
struct str {
str() = default;
str(string_view) {}
str& operator=(string_view) = delete;
};
variant<string, str> s;
s = string_view();
cout << s.index() << "\n"; // prints `1` (i.e. `str`)| From: ol...@join.cc Sent: Wednesday, May 24, 2017 10:00 AM To: ISO C++ Standard - Discussion Reply To: std-dis...@isocpp.org Subject: [std-discussion] Re: Why string_view->string is explicit? |
I think the reasoning is because there is a performance penalty that we didn't want to be silent.
I think the reasoning is because there is a performance penalty that we didn't want to be silent.Sent from my BlackBerry portable Babbage Device
From: ol...@join.ccSent: Wednesday, May 24, 2017 10:00 AMTo: ISO C++ Standard - DiscussionReply To: std-dis...@isocpp.orgSubject: [std-discussion] Re: Why string_view->string is explicit?
Not being able to call f(const string&) with a string_view is kinda annoying too..
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-discussion/YVGIEJOt_E0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-discussion+unsubscribe@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
| From: Olaf van der Spek Sent: Wednesday, May 24, 2017 12:16 PM Reply To: std-dis...@isocpp.org Subject: Re: [std-discussion] Re: Why string_view->string is explicit? |
2017-05-24 18:36 GMT+02:00 Andrey Semashev <andrey....@gmail.com>:You want errors for valid, though possibly inefficient code, right?
>> But if this is really the concern, wouldn't a warning that can be
>> disabled make more sense for such functions?
>
>
> Why would you want warnings for valid, though possibly inefficient code? The
I want something I can disable, so I can enjoy my implicit conversion.
An explicit conversion would silence the warning.
> compiler can't tell if the code intentionally calls the conversion or by
> accident. Neither it can't tell if the developer even cares.
> Right. I suppose, it's there forever now for historical reasons.Deprecation of the implicit conversion should still be possible if we
really care. Now it seems we only care a bit.
On Wednesday, 24 May 2017 10:11:58 PDT Joseph Thomson wrote:
> There are a number of problems with the current solution. Firstly, it
> doesn't address the general case (conversion to `char const*`).
As Olaf said, that constructor could be deprecated and/or made explicit.
> Secondly,
> it will often have the opposite effect to that which is intended
> (introduction of inefficient casts into user code).
Sorry, how would the explicit cast be less efficient than the implicit one?
> Thirdly, it will
> prevent at least one actual optimisation that could be added to the STL
> (relating to homogeneous keys).
Please explain this one. It's not obvious what optimisation you're talking
about.
> Lastly, it's inconsistent and inconvenient
> (sometimes you want to convert to `string`).
And sometimes you don't: you want to be told when you need to add a
string_view overload. So it's convenient in that sense.
On 05/24/17 20:47, Joseph Thomson wrote:
On Wed, May 24, 2017 at 6:23 PM, Thiago Macieira <thi...@macieira.org <mailto:thi...@macieira.org>> wrote:
> Secondly,
> it will often have the opposite effect to that which is intended
> (introduction of inefficient casts into user code).
Sorry, how would the explicit cast be less efficient than the
implicit one?
If I am faced with a API taking `string const&` that I cannot change to take `string_view` (this is likely to be a common scenario), I am left with no option but to cast to `string`. If at some later date, the maintainer of the API decides to use `string_view`, I now have a silent /explicit/ conversion that I wouldn't have had if the conversion had been implicit. Yes, the API maintainer could keep and deprecate (with `[[deprecated]]`) the old version, but do you really want to rely on them following the correct procedure?
I don't think you'd blindly update a third party component used in performance-critical domain without looking at the changes.
> Thirdly, it will
> prevent at least one actual optimisation that could be added to the STL
> (relating to homogeneous keys).
Please explain this one. It's not obvious what optimisation you're
talking
about.
With a `map` or `set` using `less<>`, functions such as `find` accept any type of key that is comparable with `key_type` without requiring conversion to `key_type`.
This does not require implicit conversion. The container could just forward `K&&` to the ordering predicate and `string_view` can already handle comparison/ordering with `string`.
This optimization could be extended to functions such as `try_emplace` that may insert an entry. This would require delayed (implicit) conversion of argument of type `K&&` to `key_type`, and type safety would be enforced using `enable_if_t<is_convertible_v<K&&, key_type>>`. Obviously, `string_view` is not currently usable with such an optimization.
This, I think, also doesn't have to require implicit conversion. `try_emplace`, as `emplace` comes down to constructing `pair<key_type, value_type>(piecewise_construct, tuple<string_view>, tuple<Args...>)`, which should work with explicit string_view to string conversion.
> Lastly, it's inconsistent and inconvenient
> (sometimes you want to convert to `string`).
And sometimes you don't: you want to be told when you need to add a
string_view overload. So it's convenient in that sense.
Static analysis tools can tell you when to do this. Also, they can tell the author of an API when the API is inefficient, rather than telling the users, who may not be able to do anything about it.
Static analysis is not a panacea. It's not much different from compiler warnings if performed just on the source code. And runtime instrumentation to detect performance impact of the conversion is not always feasible or even possible.
On Wednesday, 24 May 2017 10:47:39 PDT Joseph Thomson wrote:
> This optimization could be extended to functions such as
> `try_emplace` that may insert an entry. This would require delayed
> (implicit) conversion of argument of type `K&&` to `key_type`, and type
> safety would be enforced using `enable_if_t<is_convertible_v<K&&,
> key_type>>`. Obviously, `string_view` is not currently usable with such an
> optimization.
Because that's not an optimisation. So you construct a full object prior to
try_emplace, as opposed to constructing a full object inside try_emplace.
Where's the gain?
> Static analysis tools can tell you when to do this. Also, they can tell the
> author of an API when the API is inefficient, rather than telling the
> users, who may not be able to do anything about it.
The big problem here is that relying on static analysis tools postpones finding
solutions to problems. If the implicit conversion existed, then we'd ALL need
the static analysis to find where the expensive conversions from string_view to
string happened, so we may judge where we need to cache the string or modify
the API. I'd much rather be told up front, when writing the code, or at the
latest when code review happens.
You're also discounting the user's power here. If enough users report issues
with the API, it will get a higher priority to be fix. It's also an important
data point that the API developers may not be aware of, since they don't have
access to all their users' code.
On 05/24/17 21:39, Joseph Thomson wrote:
On Wed, May 24, 2017 at 7:09 PM, Andrey Semashev <andrey....@gmail.com <mailto:andrey.semashev@gmail.com>> wrote:
On 05/24/17 20:47, Joseph Thomson wrote:
If I am faced with a API taking `string const&` that I cannot
change to take `string_view` (this is likely to be a common
scenario), I am left with no option but to cast to `string`. If
at some later date, the maintainer of the API decides to use
`string_view`, I now have a silent /explicit/ conversion that I
wouldn't have had if the conversion had been implicit. Yes, the
API maintainer could keep and deprecate (with `[[deprecated]]`)
the old version, but do you really want to rely on them
following the correct procedure?
I don't think you'd blindly update a third party component used in
performance-critical domain without looking at the changes.
Isn't this the problem that is being addressed by disallowing this implicit conversion? Programmers not realizing when an API has started using `string_view`?
I don't think I see the problem.
When the conversion is explicit (which is now), if the API changes to require the conversion I can immediately see that because my code fails to compile. This is good because now that I'm aware of the change I may adjust the code so that the performance impact is minimal.
When the conversion is implicit, the API change goes unnoticed until I find that performance has dropped.
This optimization could be extended to functions such as
`try_emplace` that may insert an entry. This would require
delayed (implicit) conversion of argument of type `K&&` to
`key_type`, and type safety would be enforced using
`enable_if_t<is_convertible_v<K&&, key_type>>`. Obviously,
`string_view` is not currently usable with such an optimization.
This, I think, also doesn't have to require implicit conversion.
`try_emplace`, as `emplace` comes down to constructing
`pair<key_type, value_type>(piecewise_construct, tuple<string_view>,
tuple<Args...>)`, which should work with explicit string_view to
string conversion.
If we don't check for convertibility, we compromise the type safety of `try_emplace`, as it will not currently accept keys that are only explicitly convertible to `key_type`.
You can check with is_constructible instead of is_convertible. I would say, this condition would be more correct regardless of the topic anyway.
Static analysis is not a panacea. It's not much different from
compiler warnings if performed just on the source code. And runtime
instrumentation to detect performance impact of the conversion is
not always feasible or even possible.
Compiler/static analysis warnings are great when they very rarely give false positives. Conversion from `string_view` to `string const&` is always (AFAIK) less efficient than not converting at all, so this seems like an ideal use case for static analysis.
It is on paper but in reality it may not be as useful. I doubt the analyzer will be able to distinguish cases where the implicit conversion cannot be further optimized (e.g. string_view is implicitly converted to string once, and there's no alternative way to do what you want) from cases where it can. The result will be either lots of false positives (and thus encouraging the annoyed user disabling the diagnostics) or false negatives (and thus little benefit from the diagnostics).