Why string_view->string is explicit?

1,552 views
Skip to first unread message

TONGARI J

unread,
Mar 14, 2017, 4:15:27 AM3/14/17
to ISO C++ Standard - Discussion
P0254 does give a reason:
Creating a string from a string_view is not cheap, so it should be explicit

But given that we already have an implicit ctor that takes a char-pointer, I don't think that argument makes sense (unless you also want to deprecate that one).
I think the ease of use is more feasible than some unintuitive restriction.

Has this ever been discussed in the standard committee?

Tony V E

unread,
Mar 14, 2017, 7:56:43 AM3/14/17
to ISO C++ Standard - Discussion
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?'


Sent from my BlackBerry portable Babbage Device
From: TONGARI J
Sent: Tuesday, March 14, 2017 4:15 AM
To: ISO C++ Standard - Discussion
Subject: [std-discussion] Why string_view->string is explicit?

--

---
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-discussio...@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/.

TONGARI J

unread,
Mar 14, 2017, 10:17:43 AM3/14/17
to ISO C++ Standard - Discussion
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.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.

Look at what it is:
string_view sv(...);
string s = sv; // not allowed
s
= sv; // allowed

A wat moment.

Vicente J. Botet Escriba

unread,
Mar 14, 2017, 2:07:48 PM3/14/17
to std-dis...@isocpp.org
Le 14/03/2017 à 15:17, TONGARI J a écrit :
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.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.
Humm. I agree that if all interface requested a copy, then whether the conversion throws wouldn't have any importance. However take this case

    void f(T const&);

If I have a T t, I can call f(t) without copying it and of course with no exception thrown. However if I have a U u that is implicitly convertible to T, I could call f(u) but I will need to convert it and then have possible exceptions.

Having an explicit conversion wouldn't compile and so the user would need to analyze if she wants to do an explicit conversion that can cost and throw.

    f(T(u));

I agree that sometimes we want to be as transparent as possible, but I don't believe having this conversion implicit is good. As Tony said, the conversion from char const* should be as well explicit.


 
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.
I agree. IMO this merits a DR.
The assignment string::opetaror=(string_view) should be removed.

Vicente

Tony V E

unread,
Mar 14, 2017, 2:22:44 PM3/14/17
to std-discussion
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.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.

By what semantic?  That type two types represent the "same thing", or something else?
 
 
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; // allowed

A wat moment.


That's a good point.
My conclusion from that might be that


string s = sv; // not allowed

should be allowed on explicit constructors.
(But I know changing that is a significant change).
Not just because it goes along with operator=, but because most devs consider

string s = sv;

to be "the same as"

string s{sv};



--
Be seeing you,
Tony

Vicente J. Botet Escriba

unread,
Mar 14, 2017, 2:57:01 PM3/14/17
to std-dis...@isocpp.org
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?"



However until we change this the operator= shouldn't be defined if the conversion is explicit.

Tony, I believe that this could be added to the recommendations of your paper. What do you think?

Vicente

Vicente

Ville Voutilainen

unread,
Mar 14, 2017, 3:00:13 PM3/14/17
to std-dis...@isocpp.org
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.

> 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.

Nicol Bolas

unread,
Mar 14, 2017, 3:55:30 PM3/14/17
to ISO C++ Standard - Discussion


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.

Vicente J. Botet Escriba

unread,
Mar 14, 2017, 5:57:26 PM3/14/17
to std-dis...@isocpp.org
Le 14/03/2017 à 20:55, Nicol Bolas a écrit :


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.

Thanks for the example. Note that I'm not proposing it. I didn't know of a case where changing this could break. The example is weird and I don't see a real case where this could be useful, but I suspect there should be code like this one on the field.


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.
I believe we agree all here.


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.
This is exactly my concern and I don't believe the assignment is useful.  
Think of chrono::duaration, do we expect it to be assignable from its representation?

Vicente

TONGARI J

unread,
Mar 14, 2017, 10:19:27 PM3/14/17
to ISO C++ Standard - Discussion
On Wednesday, March 15, 2017 at 2:22:44 AM UTC+8, Tony VE wrote:


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.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.

By what semantic?  That type two types represent the "same thing", or something else?

A unary ctor is either for conversion or construction.
Conversion is typically for types in the same category, and construction is typically for orthogonal types.
And for conversion, if the conversion does not lose information in the type's domain, it should be implicit.

For example, consider a graphics library:

graphic::path path = graphic::ellipse(center, r);

graphic::path is a container (would require heap allocation), and a graphic::ellipse describes an ellipse by its center and radius.
They all represent graphic paths, and an ellipse can be represented as a general path, so the graphic::ellipse->graphic::path conversion should be implicit.
One may argue that the conversion from ellipse to general path does lose the information of center and radius, but the domain here is GraphicPath, in that sense, it's lossless.

On the other hand, consider:

asio::io_service io;
asio
::ip::tcp::socket sock(io);

io_service and socket are orthogonal types. socket(io_service&) is construction, as conversion doesn't make sense, so it should be explicit. 

TONGARI J

unread,
Mar 14, 2017, 10:49:06 PM3/14/17
to ISO C++ Standard - Discussion
It's useful performance-wise. Such an assignment could reuse the allocated memory.
My preference is to remove the explicit restriction from the ctor, instead of adding another restriction by removing the assignment.

I don't think trading the ease-of-use and performance for some imaginary safety is worthwhile, after all, we're in C++.

joseph....@gmail.com

unread,
Mar 22, 2017, 3:56:07 AM3/22/17
to ISO C++ Standard - Discussion

I am re-posting here and deleting my original post, as I didn't realise my thread was a duplicate.

With regard to this post, I have noticed that the inability of `string_view` to convert to `string` causes a problem when considering interoperation with hypothetical heterogeneous associative container modifiers, such as:

template <typename K>
T
& map::operator[](K const& k);

A natural requirement of this operator is that `is_convertible_v<K, key_type>` be `true`. However, to support use of `string_view` (my motivating use case) with this operator, the requirement must be weakened to `is_constructible_v<key_type, K>` being `true`. This seems wrong, yet it seems natural that `string_view` should be usable as a key.

Why is `string_view` not convertible to `string`? I have found this discussion from 2014 where Matthew Fioravante gives this rationale:

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.

I don't find this reasoning convincing. The same logic could be applied to conversion of `char const*` to `string`, yet this is permitted. Is the opinion of the committee that conversion of `char const*` to `string` was a mistake? As an example, say I have a function:

void f(string);

I can call this function in three ways:

f(s1);  // `decltype(s1)` is `char const*`
f
(s2);  // `decltype(s2)` is `string`
f
(s3);  // `decltype(s3)` is `string_view`

In each of these cases, an allocation and a copy is made, but only in the case of `string_view` is the programmer made explicitly aware of this. Even if the function looks like this instead:

void f(string const&);

There is still an allocation and a copy made in the `char const*` case. And in this case, I would say that the function should be updated like so:

void f(string_view);

This way no allocations or copies are made.

Also, if `string_view` should be thought of as a reference-like type, does that mean that conversion from `T&` to `T` should be explicit?

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.

joseph....@gmail.com

unread,
Mar 22, 2017, 5:01:25 AM3/22/17
to ISO C++ Standard - Discussion
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.

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.

Nicol Bolas

unread,
Mar 22, 2017, 9:06:54 AM3/22/17
to ISO C++ Standard - Discussion, joseph....@gmail.com
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

joseph....@gmail.com

unread,
Mar 22, 2017, 9:45:57 PM3/22/17
to ISO C++ Standard - Discussion, joseph....@gmail.com

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.


Nicol Bolas

unread,
Mar 23, 2017, 2:02:04 AM3/23/17
to ISO C++ Standard - Discussion, joseph....@gmail.com
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`.
 
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.

That's a fair point. But it still has to be weighed against the performance issue: do we want to make costly operations appear cheap? Which is more important: future performance, or current performance due to misusing implicit conversions?

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?

Vicente J. Botet Escriba

unread,
Mar 23, 2017, 2:52:55 AM3/23/17
to std-dis...@isocpp.org
Le 22/03/2017 à 10:01, joseph....@gmail.com a écrit :
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.
I think that the performance and the exception safety criteria should be compared to the ones of the ToType copy constructor.
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.


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.
This is the problem. We cannot.

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).
Compilers can optimize code in case the code is noexcept. Using a function that takes string_view instead will help.


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.
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.


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.

Best,
Vicente

TONGARI J

unread,
Mar 23, 2017, 3:50:34 AM3/23/17
to ISO C++ Standard - Discussion
On Thursday, March 23, 2017 at 2:52:55 PM UTC+8, Vicente J. Botet Escriba wrote:
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.

Functions that takes "string const&" probably would never vanish as long as we have to deal with traditional C functions that takes "char const*" which is assumed to be null-terminated.
In that case, "string const&" is the best choice since it's guaranteed to be null-terminated.

joseph....@gmail.com

unread,
Mar 23, 2017, 4:30:42 AM3/23/17
to ISO C++ Standard - Discussion, joseph....@gmail.com
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.

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`?
 
I disagree. A `string_view` is conceptually the same thing as a `string const`. They should support the same basic operations. The only difference is that one owns the underlying data. Conversely, a range is simply something that can be iterated over, while a container could be any number of things. If you enabled conversion from range to container, you would enable things like conversion of a `map` to a `vector`. A `map` is not the same thing as a `vector`. They are totally different data structures supporting different operations. `string_view` and `string` are the same kind of thing; they (should) support the same operations.

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`.
 
Refactoring to use a new feature is generally a gradual process. In a large code base, it is impractical to change everything at once. By "cannot be helped", I mean that the programmer cannot personally refactor the interfaces of the library. Maybe he reported an issue with the library maintainer, and went ahead with the refactor because it would result in net performance gains despite the rough edges.

joseph....@gmail.com

unread,
Mar 23, 2017, 4:40:59 AM3/23/17
to ISO C++ Standard - Discussion, joseph....@gmail.com

I think enabling conversions that are conceptually correct and safe, avoiding casts wherever possible, and relying on the type system as much as possible is what is important. Performance can be addressed on a case-by-case basis by individual users by profiling. Disabling this conversion seems kind of like library designers prematurely optimizing user code.
 
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.

This is a fair point, but it would have the side effect of flagging all calls to `frobnicate` with a `string` as deprecated. This would require the user to cast each `string` to a `string_view` which, while not as egregious as the inverse, is still not great.
 
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?

The criteria for determining whether a conversion should be supported should not include performance considerations. I was just showing how disabling the conversion can adversely affect performance. Even if this weren't the case, my view would be the same.

joseph....@gmail.com

unread,
Mar 23, 2017, 4:58:01 AM3/23/17
to ISO C++ Standard - Discussion
On Thursday, 23 March 2017 14:52:55 UTC+8, Vicente J. Botet Escriba wrote:
Le 22/03/2017 à 10:01, joseph....@gmail.com a écrit :
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.
I think that the performance and the exception safety criteria should be compared to the ones of the ToType copy constructor.
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.

I posted an example in reply to Nicol using this exact case (adding extra overloads) where the lack of an implicit conversion results in reduced efficiency and exception safety. As I mentioned, casting (as the name implies) makes code rigid and inflexible, and prevents the type system from doing its job correctly. Casting should be reserved for conversions that are not strictly correct or are unsafe.
 
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.

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.
This is the problem. We cannot.

You can disable copy construction/assignment and replace it with a `clone` method. This way users can't accidentally make an expensive copy. Would you recommend this for any type that is expensive to copy? If so, how would you recommend library designers determine what constitutes "expensive"?

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).
Compilers can optimize code in case the code is noexcept. Using a function that takes string_view instead will help.

That's true, but it has nothing to do with what we are talking about.

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.
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.

You achieve this goal even if you enable the conversion.

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.

I still think it will hurt adoption. `string_view` should interoperate smoothly with existing code.
 

Best,
Vicente

Bo Persson

unread,
Mar 23, 2017, 5:23:29 AM3/23/17
to std-dis...@isocpp.org
On 2017-03-23 02:45, joseph....@gmail.com wrote:

> 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.
>


The general idea is to use a string_view as an optimization when you
don't want to pass strings around. Not implicitly converting it back to
a string again is *totally* consistent.



Bo Persson



Richard Hodges

unread,
Mar 23, 2017, 5:46:15 AM3/23/17
to std-dis...@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 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.

Joseph Thomson

unread,
Mar 23, 2017, 11:41:49 AM3/23/17
to std-dis...@isocpp.org
On 23 Mar 2017 5:46 pm, "Richard Hodges" <hodg...@gmail.com> wrote:

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.

You are talking about commutativity. However, this concept can only be applied to binary operations. A type conversion is not a binary operation; it is a mapping.

If you want to establish a mathematical basis for type conversions, you probably want to look at the concept of homomorphisms. A homomorphism is a structure-preserving mapping between two algebraic structures of the same type. For example, if you have a homomorphism, f : A -> B, that maps structure A to structure B, it will preserve the operations. For example, if A and B define addition then, given every pair of elements of A, x and y:

  f(x + y) = f(x) + f(y)

If you apply this concept to the C++ type system, `A` and `B` are types that meet the requirements of a shared concept, and the homomorphism `f` is the conversion from `A` to `B`.

In our case, `string_view` and `string` are both conceptually "strings" (they are the same kind of thing), and a conversion (homomorphism) does exist from `string_view` to `string`, because the conversion maintains the string operations (e.g. `length`, `substr`, `operator[]`, `operator+`). In fact, a two-way conversion (isomorphism) between `string_view` and `string` exists because `string` can also convert to `string_view` (which cannot be said for other related pairs of types, like `int` and `short`).

Of course, exceptions throw a spanner in the works, because there is no mathematical analogue (to my knowledge). I strongly believe that conversions should be allowed to throw. This way, conversion can be allowed for a partial mapping, f, where f(x) is undefined for some values of x (for example, Robert Ramey's proposed Boost Safe Numerics library uses exceptions like this to great effect, where for example, conversion from `safe<long>` to `safe<short>` is permitted but may throw).

Conversion of value semantic types is a mathematical concept. Performance concerns should not factor into the equation.

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).

`string_view` and `string` have value semantics. In other words:

  string s = sv;
  assert(s == sv);

They are not like pointers, where value is equivalent to the identity of the underlying data. Two `string_view`s that internally reference the same data are no more related than two `string`s of equal value. Equality is what matters, not the underlying representation. The are no side effects.

--

---
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.

Nicol Bolas

unread,
Mar 23, 2017, 11:49:10 AM3/23/17
to ISO C++ Standard - Discussion

I wrote a `zstring_view` class for exactly this purpose. It's almost exactly like `string_view`, except that it's guaranteed to be null-terminated. And it removes/modifies a few APIs to ensure this (you can't remove characters from the end of a `zstring_view`, and sub-stringing it returns a `string_view`).

Nicol Bolas

unread,
Mar 23, 2017, 12:03:52 PM3/23/17
to ISO C++ Standard - Discussion, joseph....@gmail.com
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".

Richard Hodges

unread,
Mar 23, 2017, 12:16:08 PM3/23/17
to std-dis...@isocpp.org
You are talking about commutativity

Thank 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".


Joseph Thomson

unread,
Mar 23, 2017, 12:20:01 PM3/23/17
to Nicol Bolas, ISO C++ Standard - Discussion
On 24 Mar 2017 12:03 am, "Nicol Bolas" <jmck...@gmail.com> wrote:
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.

A `string` is a range too. A range is just a type that defines `std::begin` and `std::end` (this may or may not be technically correct, but you get my point). If `string_view` looks and behaves like a "string", then it is a 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

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".

Conversions are implicit. I did use the term "explicit conversion", but this isn't really a thing. An "explicit conversion" is a construction, and construction syntax is unfortunately the same as type casting syntax (unless you use uniform initialization syntax, which is its own can of worms). Conversion is the natural way to convert one type to another. Conversion from `string_view` to `string` is currently disabled. To perform the"conversion" you must explicitly invoke the constructor/perform a your cast.

Joseph Thomson

unread,
Mar 23, 2017, 12:48:46 PM3/23/17
to std-dis...@isocpp.org
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`.

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.

The key difference between `string_view` or `string const&` or `reference_wrapper<string>` and a pointer is how comparison behaves. A pointer has reference comparison semantics (the identity of the referenced object is compared), while all the other types have value comparison semantics (the value of the referenced object is compared). This is a very important distinction.

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.

You have to be careful when working with references of any form in C++, and `string_view` is no exception. And anyway, it could be argued that forbidding conversion to `string` will only make it more likely that the user will hold on to a dangling `string_view`. If you want to discourage dangling `string_view`s, disable conversion from `string` to `string_view` instead.


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".

Copying a pointer never results in a copy of the referenced object, which is why this would be surprising. `string_view` is a reference-like type, not a pointer-like type, as you pointed out, and copying a reference results in a copy of the referenced object if the target object is not itself reference-like. So that conversion from `string_view` to `string` be enabled, and that it makes a copy of the referenced object, is not unusual in this regard.

Chris Hallock

unread,
Mar 23, 2017, 12:53:35 PM3/23/17
to ISO C++ Standard - Discussion, joseph....@gmail.com
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?

Joseph Thomson

unread,
Mar 23, 2017, 1:01:04 PM3/23/17
to std-dis...@isocpp.org
On 24 Mar 2017 12:53 am, "Chris Hallock" <christoph...@gmail.com> wrote:
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?

That's up to the authors of those string types. The conversion can be implemented as a constructor of the 3rd party type, so `string_view` doesn't have to worry about it.

Richard Hodges

unread,
Mar 23, 2017, 1:27:46 PM3/23/17
to std-dis...@isocpp.org
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 have to say that in any other language, I would probably take your position too. Perhaps there is something about my perception of c++ (many years of getting fingers burned in c perhaps?) that makes me lean towards explicit expressions of intent - particularly when memory allocation and object conversion/copying is concerned.

Perhaps there is an ideological case to explore whether construction from references should be deprecated in favour of construction from copy_wrappers - consider:

auto x = move(y);   // definitely a move

auto p = copy(q);   // definitely a copy

 a = a_type { copy(b), move(c) };  // definitely copied and moved 

auto r = s;  // illegal for non-arithmetic types

... but then maybe this is the first step on the road to COBOL++


Nicol Bolas

unread,
Mar 23, 2017, 2:56:25 PM3/23/17
to ISO C++ Standard - Discussion, joseph....@gmail.com

The explicit conversion from `string_view` to `string` is implemented in `string`, not `string_view`. As would be the implicit form. Since `string_view` is the more fundamental type, it is the higher-level types that have to provide conversions from and to `string_view`.

Chris Hallock

unread,
Mar 23, 2017, 3:05:08 PM3/23/17
to ISO C++ Standard - Discussion, joseph....@gmail.com
On Thursday, March 23, 2017 at 1:01:04 PM UTC-4, Joseph Thomson wrote:


Of course, but do you have a recommended design direction with respect to whether any string type (not just std::string) should implicitly construct from string_view?

Matthew Woehlke

unread,
Mar 23, 2017, 3:28:17 PM3/23/17
to std-dis...@isocpp.org
(@Joseph, can you please configure your mailer to quote properly? There
is zero distinction between your text and text to which you are replying...)

On 2017-03-23 15:05, Chris Hallock wrote:
> On Thursday, March 23, 2017 at 1:01:04 PM UTC-4, Joseph Thomson wrote:
>> On 24 Mar 2017 12:53 am, "Chris Hallock" wrote:
>>> 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?
>>
>> That's up to the authors of those string types. The conversion can
>> be implemented as a constructor of the 3rd party type, so
>> `string_view` doesn't have to worry about it.
>
> Of course, but do you have a *recommended design direction* with
> respect to whether any string type (not just std::string) should
> implicitly construct from string_view?

Here's a silly suggestion... implicit conversion constructions should be
consistent.

I find it strange that these:

char* -> string : implicit
string -> char* : explicit
string_view -> string : explicit
string -> string_view : implicit

...would be inconsistent.

--
Matthew

inkwizyt...@gmail.com

unread,
Mar 23, 2017, 5:20:07 PM3/23/17
to ISO C++ Standard - Discussion, joseph....@gmail.com

Another way to have cake and eat cake is introduce helper function that will allow implicit cast even if explicit is only available:

#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

But probably this is not most sane thing to do because explicit constructors have great value on its own and exist for the reason.

Vicente J. Botet Escriba

unread,
Mar 23, 2017, 6:07:00 PM3/23/17
to std-dis...@isocpp.org
Le 23/03/2017 à 17:48, Joseph Thomson a écrit :
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`.

No. It is convertible to T&

operator T& () const;

Vicente

joseph....@gmail.com

unread,
Mar 23, 2017, 7:44:22 PM3/23/17
to ISO C++ Standard - Discussion

static_assert(is_convertible_v<reference_wrapper<string>, string>);

Nicol Bolas

unread,
Mar 23, 2017, 8:37:51 PM3/23/17
to ISO C++ Standard - Discussion

There are a lot of factors that go into whether a conversion should be implicit or explicit. While consistency is important, we must also look at the differences between the things you want to analogize.

There are lots of APIs that take a `char*` which would not be an appropriate target for an implicit conversion from `string`. This is in large part due to the fact that `char` is overloaded to mean both "character" and "byte". So an implicit conversion to `char*` is more dangerous than an implicit conversion to a type that is similar in form to `char*` but isn't actually a `char*`.

Conversion to `char*` also loses information, since it has no explicit length, whereas conversion to `string_view` preserves all pertinent information.

Pre-UDLs, I would argue that the implicit conversion from `char*` to `string` was just an unfortunate necessity, for the sake of preserving user sanity. But now that we can do `"Some String"s` to make `std::string`s, the verbosity of the explicit conversion is no longer necessary for string literals. So it would only be needed for cases where you get a `char*` from someone else. So making it explicit would not be as painful of a thing.

Patrice Roy

unread,
Mar 23, 2017, 8:57:25 PM3/23/17
to std-dis...@isocpp.org
string_view from string is essentially free. string from string_view is not. We're talking two different beasts here, and it's good that one is implicit and no cost and the other, where it seems like a good idea, is explicit as it bears cost.

I can understand the discourse that tries to push for an implicit construction of string from string_view, but I think it's preferable to keep this conversion explicit, in order to avoid unforeseen costs.

--

joseph....@gmail.com

unread,
Mar 23, 2017, 9:16:51 PM3/23/17
to ISO C++ Standard - Discussion

I couldn't have said it better myself. Strictly, conversion from `char const*` is incorrect, but it was a necessary fudge given the nastiness inherited from C.

joseph....@gmail.com

unread,
Mar 23, 2017, 9:20:19 PM3/23/17
to ISO C++ Standard - Discussion, joseph....@gmail.com

If the string type is essentially similar to `string`, then it would probably be desirable to support conversion from `string_view`. There may be additional considerations depending on the exact design of the string type.

joseph....@gmail.com

unread,
Mar 23, 2017, 9:25:45 PM3/23/17
to ISO C++ Standard - Discussion, joseph....@gmail.com

 I agree, but this is the only real option, unless there were some formally defined `String` concept that could be checked at compile-time.

joseph....@gmail.com

unread,
Mar 23, 2017, 9:30:03 PM3/23/17
to ISO C++ Standard - Discussion

On Friday, 24 March 2017 03:28:17 UTC+8, Matthew Woehlke wrote:
(@Joseph, can you please configure your mailer to quote properly? There
is zero distinction between your text and text to which you are replying...)

Really? I'm just using the plain old Gmail web client. It all looks okay to me, both in Gmail and on Google Groups.

joseph....@gmail.com

unread,
Mar 23, 2017, 10:14:08 PM3/23/17
to ISO C++ Standard - Discussion
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:

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;
}

Is it reasonable to expect the average user to do this just to get good performance? And I'm sure there will be other places where inability to convert `string_view` to `string` will have a knock-on effect.

Of course, you could weaken the requirement for `operator[]` to `is_constructible_v<key_type, K>`, but would be letting the poor design choices for `string_view` influence poor design choices (weakened type safety) for `map`. This is the result of not consistently following correct design principles.

Nicol Bolas

unread,
Mar 23, 2017, 11:48:26 PM3/23/17
to ISO C++ Standard - Discussion, joseph....@gmail.com
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.

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`.

joseph....@gmail.com

unread,
Mar 24, 2017, 3:23:29 AM3/24/17
to ISO C++ Standard - Discussion, joseph....@gmail.com
On Friday, 24 March 2017 11:48:26 UTC+8, Nicol Bolas wrote:
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.

This is fine for `string_view`, because creating a `string` from a `string_view` is safe (which is why conversion should be supported). This is not always the case. Consider a integral wrapper type `safer<T>`, which makes narrowing conversions explicit:

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.

As in the above example, conversion need only be supported in one direction (homomorphism) to imply comparability. Conversion in both directions (isomorphism) may be supported, but it isn't necessary. In other words, two types are comparable if conversion is supported in at least one direction.
 
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`.

I'm not sure what you mean here. The comparison object shouldn't do any explicit conversion. Conversion is only required because a copy of the key might be stored.

joseph....@gmail.com

unread,
Mar 24, 2017, 8:47:20 AM3/24/17
to ISO C++ Standard - Discussion, 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'm probably just going to go ahead and file a defect report.

Olaf van der Spek

unread,
Mar 24, 2017, 9:22:57 AM3/24/17
to ISO C++ Standard - Discussion, joseph....@gmail.com


Op vrijdag 24 maart 2017 13:47:20 UTC+1 schreef joseph....@gmail.com:
string s = sv not being allowed is an unfortunate side-effect, it's (AFAIK) not the reason for the explicit constructor from string_view.

void f(const string& s);

Isn't the real problem this construct allowing implicit conversion?
What if, like non-const&, const& in general didn't allow such conversions?

Maybe explicit is too overloaded.. ;)

Daniel Krügler

unread,
Mar 24, 2017, 9:30:54 AM3/24/17
to std-dis...@isocpp.org, joseph....@gmail.com
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);

- Daniel

Ville Voutilainen

unread,
Mar 24, 2017, 9:34:38 AM3/24/17
to std-dis...@isocpp.org
The explicit conversion from string_view to string is not a defect,
it's intentional.
Write a paper.

Joseph Thomson

unread,
Mar 24, 2017, 9:45:26 AM3/24/17
to ISO C++ Standard - Discussion
Thanks. Even if it isn't a defect, I worry that it is a decision (mistake in my opinion) that may be irreversible because of user code breakage. Could you provide any insight into the rationale behind the decision? It would be of great help if I am going to make a case against it.

Ville Voutilainen

unread,
Mar 24, 2017, 9:47:11 AM3/24/17
to std-dis...@isocpp.org
On 24 March 2017 at 15:45, Joseph Thomson <joseph....@gmail.com> wrote:
>> The explicit conversion from string_view to string is not a defect,
>> it's intentional.
>> Write a paper.
>
>
> Thanks. Even if it isn't a defect, I worry that it is a decision (mistake in
> my opinion) that may be irreversible because of user code breakage. Could
> you provide any insight into the rationale behind the decision? It would be
> of great help if I am going to make a case against it.


The rationale is that the conversion allocates memory, so it shouldn't
happen silently
in function calls.

Joseph Thomson

unread,
Mar 24, 2017, 9:48:37 AM3/24/17
to ISO C++ Standard - Discussion
Thanks for confirming this.

Olaf van der Spek

unread,
Mar 24, 2017, 9:50:58 AM3/24/17
to std-dis...@isocpp.org
Right, so what's the plan for f("X") when the type is const string&?


--
Olaf

Matthew Woehlke

unread,
Mar 24, 2017, 9:57:38 AM3/24/17
to std-dis...@isocpp.org
On 2017-03-24 09:22, Olaf van der Spek wrote:
> Op vrijdag 24 maart 2017 13:47:20 UTC+1 schreef 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.
>
> string s = sv not being allowed is an unfortunate side-effect, it's (AFAIK)
> not the reason for the explicit constructor from string_view.
>
> void f(const string& s);

As I said elsewhere:

void f(const string& s);

f("hello, world!"); // okay

string_view sv = "hello, world!";
f(sv); // error?!

Yes, I hear you saying "in a perfect world neither of those would be
okay". Well, I'm unconvinced that all the rampant inconsistency is worth
the theoretical performance advantage, especially since Joseph showed a
case where the lack of conversion leads to explicit construction, which
later becomes a pessimization itself.

This:

void f(const string&); // legacy
void f(string_view);

string_view sv = ...;
f(string(sv)); // string_view overload didn't exist when written

...is unacceptable IMHO. We either need a way, *now*, for compilers to
diagnose this, or we need to not cause it in the first place.

--
Matthew

Ville Voutilainen

unread,
Mar 24, 2017, 10:07:34 AM3/24/17
to std-dis...@isocpp.org
On 24 March 2017 at 15:50, Olaf van der Spek <olafv...@gmail.com> wrote:
>> The rationale is that the conversion allocates memory, so it shouldn't
>> happen silently
>> in function calls.
>
> Right, so what's the plan for f("X") when the type is const string&?


There is no plan to change it.

Olaf van der Spek

unread,
Mar 24, 2017, 10:09:57 AM3/24/17
to std-dis...@isocpp.org
It's still 'bad' isn't it?
Do compilers warn for it? Do code analyzers?

I don't entirely get the discrepancy between const char* and string_view here.




--
Olaf

Ville Voutilainen

unread,
Mar 24, 2017, 10:18:38 AM3/24/17
to std-dis...@isocpp.org
On 24 March 2017 at 16:09, Olaf van der Spek <olafv...@gmail.com> wrote:
>>> Right, so what's the plan for f("X") when the type is const string&?
>>
>>
>> There is no plan to change it.
>
> It's still 'bad' isn't it?

Is it? If the literal is short, there's no allocation since it fits
into an SSO string.

> Do compilers warn for it? Do code analyzers?

I doubt compilers warn about it since that would generate fair amounts
of false positives.
I am unaware of any special warning flags that would warn on it, and I
have no idea whether
analyzers diagnose it.

> I don't entirely get the discrepancy between const char* and string_view here.

The one we can't change for compatibility reasons, the other we can.

Matthew Woehlke

unread,
Mar 24, 2017, 10:20:40 AM3/24/17
to std-dis...@isocpp.org
On 2017-03-23 20:37, Nicol Bolas wrote:
> On Thursday, March 23, 2017 at 3:28:17 PM UTC-4, Matthew Woehlke wrote:
>> I find it strange that these:
>>
>> char* -> string : implicit
>> string -> char* : explicit
>> string_view -> string : explicit
>> string -> string_view : implicit
>>
>> ...would be inconsistent.
>
> Conversion to `char*` also loses information, since it has no explicit
> length, whereas conversion to `string_view` preserves all pertinent
> information.

In that sense, conversion from a char* to a string is hard, because you
have to count the characters to know the length. Having implicit
conversion from char* to string, but *not* from string_view to string,
is, in that respect... well, "questionable" would be putting it mildly...

> Pre-UDLs, I would argue that the implicit conversion from `char*` to
> `string` was just an unfortunate necessity, for the sake of preserving user
> sanity. But now that we can do `"Some String"s` to make `std::string`s, the
> verbosity of the explicit conversion is no longer necessary for string
> literals. So it would only be needed for cases where you get a `char*` from
> someone else. So making it explicit would not be as painful of a thing.

Then let's do that.

--
Matthew

Olaf van der Spek

unread,
Mar 24, 2017, 10:23:55 AM3/24/17
to std-dis...@isocpp.org
2017-03-24 15:18 GMT+01:00 Ville Voutilainen <ville.vo...@gmail.com>:
> On 24 March 2017 at 16:09, Olaf van der Spek <olafv...@gmail.com> wrote:
>>>> Right, so what's the plan for f("X") when the type is const string&?
>>>
>>>
>>> There is no plan to change it.
>>
>> It's still 'bad' isn't it?
>
> Is it? If the literal is short, there's no allocation since it fits
> into an SSO string.

If it's a literal you'd think the compiler could construct it at compile-time.
But it could be a const char* too..

BTW, SSO works for string_view too.

>> Do compilers warn for it? Do code analyzers?
>
> I doubt compilers warn about it since that would generate fair amounts
> of false positives.
> I am unaware of any special warning flags that would warn on it, and I
> have no idea whether
> analyzers diagnose it.
>
>> I don't entirely get the discrepancy between const char* and string_view here.
>
> The one we can't change for compatibility reasons, the other we can.

I'm not sure there's nothing that can be done. Deprecation is one
thing that comes to mind, (optional) compiler warnings are another.



--
Olaf

Ville Voutilainen

unread,
Mar 24, 2017, 10:33:07 AM3/24/17
to std-dis...@isocpp.org
On 24 March 2017 at 16:23, Olaf van der Spek <olafv...@gmail.com> wrote:
>>> I don't entirely get the discrepancy between const char* and string_view here.
>>
>> The one we can't change for compatibility reasons, the other we can.
>
> I'm not sure there's nothing that can be done. Deprecation is one
> thing that comes to mind, (optional) compiler warnings are another.


That is, as they say, your time to waste.

Joseph Thomson

unread,
Mar 24, 2017, 10:45:21 AM3/24/17
to ISO C++ Standard - Discussion
The C++ Core Guidelines and GSL aim to address the issue of conversion from `char const*` to `string` with `gsl::zstring`:

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f25-use-a-zstring-or-a-not_nullzstring-to-designate-a-c-style-string

String literals are fine, and obviously I don't agree that their implicit conversion to `string` is incorrect.
 

Joseph Thomson

unread,
Mar 24, 2017, 10:59:54 AM3/24/17
to Daniel Krügler, ISO C++ Standard - Discussion
On Fri, Mar 24, 2017 at 9:30 PM, Daniel Krügler <daniel....@gmail.com> wrote:
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.

I'm not saying it's the same operation. But assignment is an implicit operation. If you enable conversion, you get assignment by default:

struct foo {
  foo(int);
};

foo a = 42;
a = 42; // equivalent to `a = foo(42)`

If you disable conversion, you disable assignment:

struct foo {
  explicit foo(int);
};

foo a = 42; // error
a = 42; // error

You have to explicitly enable assignment if you want to allow it again:

struct foo {
  explicit foo(int);
  foo& operator=(int);
};

foo a = 42; // error
a = 42; // okay
 
Overloading `operator=` is great for efficiency, to avoid constructing an additional temporary, but enabling it when conversion is disabled is a strange use of the type system that is not appropriate when dealing with regular types. `string` is regular. `string_view` is regular. But `string` and `string_view` do not interoperate in a regular fashion.

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);

`basic_regex` is not a regular type. Even so, use of `operator=` in this way could be considered odd in the same way that some consider overloading `operator<<` for stream objects odd. Personally, I think `basic_regex::assign` alone would have sufficed.

Nicol Bolas

unread,
Mar 24, 2017, 11:04:30 AM3/24/17
to ISO C++ Standard - Discussion, joseph....@gmail.com

The thing that allows `map<string, T, less<>>` to use `string_view` with `find` is not the fact that `string_view` has an `operator<` with `string`. It is the fact that `less<>` has an `operator()` that can take both `string` and `string_view` (or more generally, anything). Even if `string_view` itself had no `operator<` for `string`, you could create a comparison type that provides one.

So the comparison type can manufacture comparison where none would otherwise exist. So if we decide that comparison as a concept implies implicit conversion, it also makes sense to allow the comparison type to manufacture conversion where none would otherwise exist.

Nicol Bolas

unread,
Mar 24, 2017, 11:10:02 AM3/24/17
to ISO C++ Standard - Discussion

If there's an STL2 that replaces `basic_string`, then we can do that there. But until then, it's just not worth the effort. A minor gain of consistency isn't worth the pain of going through years of a deprecation/BC breakage cycle.

joseph....@gmail.com

unread,
Mar 30, 2017, 3:21:42 AM3/30/17
to ISO C++ Standard - Discussion

Dear all,

I have attached the first draft of a paper making the case for conversion of `string_view` to `string` for your viewing. Obviously, I welcome any feedback or thoughts people may have.

Thanks.
string-conversion-draft-1.html

joseph....@gmail.com

unread,
Mar 30, 2017, 3:44:10 AM3/30/17
to ISO C++ Standard - Discussion, joseph....@gmail.com

I spotted some of potentially confusing typos in the first draft. Here is the amended version.
string-conversion-draft-2.html

Ville Voutilainen

unread,
Mar 30, 2017, 4:34:08 AM3/30/17
to std-dis...@isocpp.org, Joseph Thomson
On 30 March 2017 at 10:44, <joseph....@gmail.com> wrote:
>> Dear all,
>>
>> I have attached the first draft of a paper making the case for conversion
>> of `string_view` to `string` for your viewing. Obviously, I welcome any
>> feedback or thoughts people may have.
>>
>> Thanks.
>
>
> I spotted some of potentially confusing typos in the first draft. Here is
> the amended version.


Question:

string name = wgt.get_name(); // error


Why is this a problem? Using direct-initialization will make it work:

string name{wgt.get_name()}; // ok

Olaf van der Spek

unread,
Mar 30, 2017, 4:58:06 AM3/30/17
to std-dis...@isocpp.org, Joseph Thomson
There's no direct / good reason for the former to not work. It's a
consequence of explicit being required on the constructor for other
reasons.


--
Olaf

Joseph Thomson

unread,
Mar 30, 2017, 5:05:18 AM3/30/17
to Ville Voutilainen, ISO C++ Standard - Discussion
First, it's a syntactic inconsistency. This is significant because it might affect the decision of the authors of `widget` to use `string_view` (as I mentioned, perhaps this means their priorities are wrong, but I'm sure it will be an issue for some people).

Secondly, direct initialization can cause explicit conversion (which is why you used it here). Thus, it is less safe in general. In this example, it is probably a non-issue, but it could be an issue in generic code (e.g. if `get_name` didn't always return `string_view`).

This is probably the least important issue of the lot, so I don't want to let it distract from the others.

Joseph Thomson

unread,
Mar 30, 2017, 5:33:00 AM3/30/17
to Olaf van der Spek, ISO C++ Standard - Discussion
Exactly. Using `string_view` as a function parameter is fine because `string` implicitly converts to `string_view`. Calling syntax is the same as if it took `string const&`. Using `string_view` as a return type means that the calling syntax is different in some cases (when converting to `string`). Either you need to use direct initialization or do this:

string s;
s = wgt.get_name();

On its own, I don't think this issue would be reason enough to argue for implicit conversion, and I certainly don't think my case depends on it. I do think it makes my case slightly stronger though, so it's worth mentioning.

joseph....@gmail.com

unread,
Apr 2, 2017, 5:38:17 AM4/2/17
to ISO C++ Standard - Discussion, olafv...@gmail.com, joseph....@gmail.com
Here is an interesting case of `string` and `string_view` interacting with `variant` that I think is pertinent to the discussion.

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`)

This potentially surprising behaviour seems to be because `variant::operator=` assumes that types that support assignment from `T` will also be convertible from `T` (it uses overload resolution as a guide to picking the target type), so it picks `str` as the target type despite it not being assignable from `string_view`. Note that deleting `str::operator=(string_view)` does actually disable `variant::operator=(string_view)`, but the assignment still works since the `variant` is still copy constructible and convertible from `string_view`. Presumably this is because `variant` assumes that a type that are not assignable from `T` will not be convertible from `T` either.

These seem like two separate issues that both stem from the assumption that conversion and assignment are either both supported or neither of them are. At any rate, if `string` were convertible from `string_view`, assignment of the `variant` would be always disabled, since `str` is also convertible from `string_view`. This would meet the expectations of `variant` and thereby avoid any potentially surprising behaviour.

ol...@join.cc

unread,
May 24, 2017, 10:00:48 AM5/24/17
to ISO C++ Standard - Discussion
Not being able to call f(const string&) with a string_view is kinda annoying too..

Tony V E

unread,
May 24, 2017, 11:16:22 AM5/24/17
to ISO C++ Standard - Discussion
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
Sent: Wednesday, May 24, 2017 10:00 AM
To: ISO C++ Standard - Discussion
Subject: [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-discussio...@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/.

Joseph Thomson

unread,
May 24, 2017, 11:39:56 AM5/24/17
to ISO C++ Standard - Discussion
On 24 May 2017 4:16 pm, "Tony V E" <tvan...@gmail.com> wrote:
I think the reasoning is because there is a performance penalty that we didn't want to be silent. 

I would like to reiterate to anyone reading that I consider this decision to be a mistake, the repercussions of which would not be insignificant. The implicit conversion is conceptually sound, and disallowing it will have a number of unfortunate side effects. I consider this to be a form of premature optimisation of user code by the committee. The correct operation of a class should not be compromised for performance reasons that may not (and I would suggest, probably won't) be of concern to the average user. The concerns of the committee would be better addressed via static analysis tools.

I've been rather busy since I started discussing this issue, so I haven't yet been able to complete my paper, not that I am sure it would have any impact on the outcome (C++17).

Andrey Semashev

unread,
May 24, 2017, 11:50:13 AM5/24/17
to std-dis...@isocpp.org, Joseph Thomson
On 05/24/17 18:39, Joseph Thomson wrote:
> On 24 May 2017 4:16 pm, "Tony V E" <tvan...@gmail.com
> <mailto:tvan...@gmail.com>> wrote:
>
> I think the reasoning is because there is a performance penalty that
> we didn't want to be silent.
>
> I would like to reiterate to anyone reading that I consider this
> decision to be a mistake, the repercussions of which would not be
> insignificant. The implicit conversion is conceptually sound, and
> disallowing it will have a number of unfortunate side effects. I
> consider this to be a form of premature optimisation of user code by the
> committee. The correct operation of a class should not be compromised
> for performance reasons that may not (and I would suggest, probably
> won't) be of concern to the average user. The concerns of the committee
> would be better addressed via static analysis tools.

I think performance penalty is enough of a reason to not perform the
conversion silently.

But then I'm generally skeptical to any implicit conversions unless
absolutely unsurprising and intended.

Olaf van der Spek

unread,
May 24, 2017, 12:16:49 PM5/24/17
to std-dis...@isocpp.org
How is it a performance penalty? If the function needs a std::string it needs a std::string.. 

2017-05-24 17:16 GMT+02:00 Tony V E <tvan...@gmail.com>:
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
Sent: Wednesday, May 24, 2017 10:00 AM
To: ISO C++ Standard - Discussion
Subject: [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/.



--
Olaf

Olaf van der Spek

unread,
May 24, 2017, 12:20:21 PM5/24/17
to std-dis...@isocpp.org, Joseph Thomson
2017-05-24 17:50 GMT+02:00 Andrey Semashev <andrey....@gmail.com>:
> On 05/24/17 18:39, Joseph Thomson wrote:
>>
>> On 24 May 2017 4:16 pm, "Tony V E" <tvan...@gmail.com
>> <mailto:tvan...@gmail.com>> wrote:
>>
>> I think the reasoning is because there is a performance penalty that
>> we didn't want to be silent.
>> I would like to reiterate to anyone reading that I consider this decision
>> to be a mistake, the repercussions of which would not be insignificant. The
>> implicit conversion is conceptually sound, and disallowing it will have a
>> number of unfortunate side effects. I consider this to be a form of
>> premature optimisation of user code by the committee. The correct operation
>> of a class should not be compromised for performance reasons that may not
>> (and I would suggest, probably won't) be of concern to the average user. The
>> concerns of the committee would be better addressed via static analysis
>> tools.
>
>
> I think performance penalty is enough of a reason to not perform the
> conversion silently.

Shouldn't we be consistent then?




--
Olaf

Tony V E

unread,
May 24, 2017, 12:21:57 PM5/24/17
to Olaf van der Spek
If you call three functions in a row that all need a string, maybe you should only convert once instead of three times.

Now maybe the committee shouldn't be worried about that.


Sent from my BlackBerry portable Babbage Device
From: Olaf van der Spek
Sent: Wednesday, May 24, 2017 12:16 PM
Subject: Re: [std-discussion] Re: Why string_view->string is explicit?

To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.

Andrey Semashev

unread,
May 24, 2017, 12:25:17 PM5/24/17
to std-dis...@isocpp.org, Olaf van der Spek, Joseph Thomson
Sure. Are we not?

Olaf van der Spek

unread,
May 24, 2017, 12:26:16 PM5/24/17
to Andrey Semashev, std-dis...@isocpp.org, Joseph Thomson
No, std::string(const char*) is implicit (AFAIK).

--
Olaf

Olaf van der Spek

unread,
May 24, 2017, 12:29:56 PM5/24/17
to std-dis...@isocpp.org
2017-05-24 18:21 GMT+02:00 Tony V E <tvan...@gmail.com>:
>
> If you call three functions in a row that all need a string, maybe you should only convert once instead of three times.

Maybe the optimizer could take care of that.
But if this is really the concern, wouldn't a warning that can be
disabled make more sense for such functions?

Andrey Semashev

unread,
May 24, 2017, 12:36:40 PM5/24/17
to std-dis...@isocpp.org, Olaf van der Spek
On 05/24/17 19:29, Olaf van der Spek wrote:
> 2017-05-24 18:21 GMT+02:00 Tony V E <tvan...@gmail.com>:
>>
>> If you call three functions in a row that all need a string, maybe you should only convert once instead of three times.
>
> Maybe the optimizer could take care of that.

I doubt this is possible unless the compiler has intrinsic knowledge
that std::string construction/destruction doesn't have any other side
effects but copying the string. I suppose, the compiler could do that
given std::string is a standard library component, but I suspect that
would be unreasonably difficult.

> 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 compiler can't tell if the code intentionally calls the conversion
or by accident. Neither it can't tell if the developer even cares.

Andrey Semashev

unread,
May 24, 2017, 12:38:06 PM5/24/17
to Olaf van der Spek, std-dis...@isocpp.org, Joseph Thomson
Right. I suppose, it's there forever now for historical reasons.

Olaf van der Spek

unread,
May 24, 2017, 1:07:27 PM5/24/17
to Andrey Semashev, std-dis...@isocpp.org
2017-05-24 18:36 GMT+02:00 Andrey Semashev <andrey....@gmail.com>:
>> 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

You want errors for valid, though possibly inefficient code, right?
I want something I can disable, so I can enjoy my implicit conversion.

> compiler can't tell if the code intentionally calls the conversion or by
> accident. Neither it can't tell if the developer even cares.

An explicit conversion would silence the warning.

> 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.

--
Olaf

Joseph Thomson

unread,
May 24, 2017, 1:12:01 PM5/24/17
to ISO C++ Standard - Discussion, Olaf van der Spek
There are a number of problems with the current solution. Firstly, it doesn't address the general case (conversion to `char const*`). Secondly, it will often have the opposite effect to that which is intended (introduction of inefficient casts into user code). Thirdly, it will prevent at least one actual optimisation that could be added to the STL (relating to homogeneous keys). Lastly, it's inconsistent and inconvenient (sometimes you want to convert to `string`). And all of this to fix a performance issue that probably isn't even an issue for most users.

Static analysis tools are the right approach. They can detect conversions from `char const*`, do not encourage use of casts, allow `string_view` to work with the optimization, allow `string_view` to be consistent and convenient to use, and can be configured by users if they don't care about the potential performance problems (which they should determine by benchmarking).

Keep in mind that disallowing the implicit conversion doesn't make `string_view` any more efficient. It just means you have to be explicit about your conversions, which is not always a good thing.

Joseph Thomson

unread,
May 24, 2017, 1:21:38 PM5/24/17
to ISO C++ Standard - Discussion
On 24 May 2017 6:07 pm, "Olaf van der Spek" <olafv...@gmail.com> wrote:
2017-05-24 18:36 GMT+02:00 Andrey Semashev <andrey....@gmail.com>:
>> 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

You want errors for valid, though possibly inefficient code, right?
I want something I can disable, so I can enjoy my implicit conversion.

This.


> compiler can't tell if the code intentionally calls the conversion or by
> accident. Neither it can't tell if the developer even cares.

An explicit conversion would silence the warning.

This also. Explicit conversion will encourage the use of casts (because we are erroneously holding users of an API responsible for the efficiency of that API). Types can and will change, resulting in redundant casts. The compiler will not warn if the casts have become redundant.

> 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.

Conversion from `char const*` is only undesirable now that we have `string_view`. It was not a mistake. Some people seem to have a strong aversion to implicit conversions, but explicit conversions (casts) result in rigid, inflexible, potentially inefficient code. Implicit casts are good where they make conceptual sense. They should not be disabled for performance reasons (IMO).

Thiago Macieira

unread,
May 24, 2017, 1:23:31 PM5/24/17
to std-dis...@isocpp.org
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.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Andrey Semashev

unread,
May 24, 2017, 1:46:27 PM5/24/17
to Olaf van der Spek, std-dis...@isocpp.org
On 05/24/17 20:07, Olaf van der Spek wrote:
> 2017-05-24 18:36 GMT+02:00 Andrey Semashev <andrey....@gmail.com>:
>>> 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
>
> You want errors for valid, though possibly inefficient code, right?

It's not a valid code because it is missing an explicit cast. :p

Seriously though, I prefer to know when I'm doing something non-trivial.
Implicit `std::string(const char*)` constructor doesn't help with this,
and I find it unfortunate because we now have plenty of APIs which
encourage this kind of conversions. Of course, at the time std::string
was introduced we had no `std::string_view`, so the design decision is
understandable. But that doesn't make the same decision nowdays as
appealing.

> I want something I can disable, so I can enjoy my implicit conversion.

I've had enough of silly compiler warnings that make no sense in real
life and only beg to be disabled. So no, thanks.

>> compiler can't tell if the code intentionally calls the conversion or by
>> accident. Neither it can't tell if the developer even cares.
>
> An explicit conversion would silence the warning.

So as it would fix the compilation error with the current state of things.

Joseph Thomson

unread,
May 24, 2017, 1:47:41 PM5/24/17
to ISO C++ Standard - Discussion
On Wed, May 24, 2017 at 6:23 PM, Thiago Macieira <thi...@macieira.org> wrote:
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.

Sorry, I meant conversion from `char const*` (though I think you realised that). It could be deprecated, but I don't think this would the correct. And even if it were, I would still recommend making conversion from `string_view` implicit.
 
> 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?
 
> 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 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.

> 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.

Virus-free. www.avast.com

Andrey Semashev

unread,
May 24, 2017, 2:10:01 PM5/24/17
to std-dis...@isocpp.org, Joseph Thomson
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.

Thiago Macieira

unread,
May 24, 2017, 2:31:26 PM5/24/17
to std-dis...@isocpp.org
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.

Joseph Thomson

unread,
May 24, 2017, 2:39:28 PM5/24/17
to ISO C++ Standard - Discussion
On Wed, May 24, 2017 at 7:09 PM, Andrey Semashev <andrey....@gmail.com> wrote:
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.
 
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`?

    > 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`.
 
Yes, there is no problem with the existing optimization.

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`.

    > 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.

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. And a static analysis warning is better than a compiler error in this case because the problem is with the API not the user code. You can ignore/temporarily disable the warning and address it later, but inserting a cast is not a great way to suppress an issue that you may want to address later.

Fundamentally, I feel that it is wrong to use a compiler error to flag a potential performance issue. A warning would be more appropriate.

Virus-free. www.avast.com

Joseph Thomson

unread,
May 24, 2017, 2:55:20 PM5/24/17
to ISO C++ Standard - Discussion
On Wed, May 24, 2017 at 7:31 PM, Thiago Macieira <thi...@macieira.org> wrote:
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?
 
Because you don't even need to perform the conversion if an entry with the given key already exists. As it stands, a conversion from `string_view` to `string` always happens at the function call site, even if the key already exists in the map.

> 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.

I would posit that use of a static analysis tool would make the author of an API aware of the problem sooner than a compiler error would. The static analysis tool can detect the problem (taking `string const&` instead of `string_view`) even if none of the author's code calls the API with `string_view`. There is no need to wait for users to report the issue.

And users can report an issue that is flagged by static analysis just as easily as one flagged by a compiler error. In fact, a warning in a header file will make it abundantly clear that it's a problem with the third party library (the tool can even state this explicitly). A compiler error makes it look like the problem is with the user code, and would be unlikely to provide any indication about an appropriate solution. For example, which of these would you rather see?

void foo(string const& s); // Warning: passing by `string const&` can be inefficient
                           // Recommendation: pass by `string_view` instead
                           // [Click here for more info]

foo(sv); // error: no conversion from `string_view` to `string const&`





Virus-free. www.avast.com

Andrey Semashev

unread,
May 24, 2017, 3:07:14 PM5/24/17
to std-dis...@isocpp.org, Joseph Thomson
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....@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).

It is my belief that whenever it comes to compiler/tool guessing the
developer's intent, chances are high the guess will be wrong. Every time
the guess is wrong I'm annoyed and inclined to just disable the warning.
So unless there is a clear-cut condition in which you absolutely do want
to be notified, warnings should be avoided, IMO.

Joseph Thomson

unread,
May 24, 2017, 3:26:12 PM5/24/17
to ISO C++ Standard - Discussion
On Wed, May 24, 2017 at 8:07 PM, Andrey Semashev <andrey....@gmail.com> wrote:
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.
 
Okay, yeah. The problem is the other way around. Still, I can see casts being inserted, becoming redundant and then going unnoticed, which means that this decision may have the opposite effect to that which was intended. And conversion to `string` is unlikely to be an issue in most cases anyway.

        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.
 
`insert_or_assign` can also use this optimization. I value type safety. What you've got if you use `is_constructible` is bad design decisions made for `string_view` propagating into other components, just so that you can maintain compatibility.

    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).
 
The tool would flag functions that take by `string const&`. This is the underlying issue. By disabling conversion to `string`, the designers of `string_view` are hoping that users will react by making their functions take `string_view` instead. There will be fewer false positives than if all conversions to `string` are flagged (with compiler errors).

Virus-free. www.avast.com

Thiago Macieira

unread,
May 24, 2017, 6:23:28 PM5/24/17
to std-dis...@isocpp.org
On Wednesday, 24 May 2017 11:55:17 PDT Joseph Thomson wrote:
> On Wed, May 24, 2017 at 7:31 PM, Thiago Macieira <thi...@macieira.org>
> > 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?
>
> Because you don't even need to perform the conversion if an entry with the
> given key already exists. As it stands, a conversion from `string_view` to
> `string` *always* happens at the function call site, even if the key
> already exists in the map.

Ah, I see what you mean. But that may be required anyway. Take an
unordered_map<string, anything>, for example. In order to look up a
string_view in that map, you'd need that

hash(sv) == hash(string(sv))

That is not guaranteed (is it?). We've had this issue for Qt: QString and
QStringView do actually produce the same hash, but that's not true between
QString and QLatin1String. So looking up a QString key (which is implicitly
constructible from QLatin1String) requires constructing a QString first.

> > 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.
>
> I would posit that use of a static analysis tool would make the author of
> an API aware of the problem sooner than a compiler error would. The static

Huh? Who in the world submits their code to static analysis before compiling
it?

> analysis tool can detect the problem (taking `string const&` instead of
> `string_view`) even if none of the author's code calls the API with
> `string_view`. There is no need to wait for users to report the issue.

Ah, you're talking about the API developer. I was talking about the user of
the API.

I'm going to grant you that library APIs are almost always of higher quality
than the user code that uses them. So I will grant you that some library
developers use static analysis tools. The problems left are:

* users most often don't, so you can't expect them to only find issues with
static analysis tools
* "some library developers" definitely does not include everyone, whereas the
population of developers that use compilers is 100%
* what kind of static analysis tools make these kind of suggestions, today?

> And users can report an issue that is flagged by static analysis just as
> easily as one flagged by a compiler error. In fact, a warning in a header
> file will make it abundantly clear that it's a problem with the third party
> library (the tool can even state this explicitly). A compiler error makes
> it look like the problem is with the user code, and would be unlikely to
> provide any indication about an appropriate solution. For example, which of
> these would you rather see?

I can argue that the problem *is* in the user code. You want the user to write
code that avoids the easy trap.

> void foo(string const& s); // Warning: passing by `string const&` can be
> inefficient
> // Recommendation: pass by `string_view` instead
> // [Click here for more info]

What tool can do that?

Johannes Schaub

unread,
May 24, 2017, 6:27:45 PM5/24/17
to std-dis...@isocpp.org
2017-05-25 0:23 GMT+02:00 Thiago Macieira <thi...@macieira.org>:
> On Wednesday, 24 May 2017 11:55:17 PDT Joseph Thomson wrote:
>> On Wed, May 24, 2017 at 7:31 PM, Thiago Macieira <thi...@macieira.org>
>> > 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?
>>
>> Because you don't even need to perform the conversion if an entry with the
>> given key already exists. As it stands, a conversion from `string_view` to
>> `string` *always* happens at the function call site, even if the key
>> already exists in the map.
>
> Ah, I see what you mean. But that may be required anyway. Take an
> unordered_map<string, anything>, for example. In order to look up a
> string_view in that map, you'd need that
>
> hash(sv) == hash(string(sv))
>
> That is not guaranteed (is it?). We've had this issue for Qt: QString and
> QStringView do actually produce the same hash, but that's not true between
> QString and QLatin1String. So looking up a QString key (which is implicitly
> constructible from QLatin1String) requires constructing a QString first.
>

According to cppreference, this is guaranteed:

"These hashes equal the hashes of corresponding std::basic_string
classes: If S is one of the standard basic_string types, SV is the
corresponding string view type, and s is an object of type S, then
std::hash<S>()(s) == std::hash<SV>()(SV(s))."

I think it would seriously be wrong if it wouldn't be that way.

Thiago Macieira

unread,
May 24, 2017, 6:36:29 PM5/24/17
to std-dis...@isocpp.org
On Wednesday, 24 May 2017 15:27:42 PDT 'Johannes Schaub' via ISO C++ Standard
- Discussion wrote:
> > That is not guaranteed (is it?). We've had this issue for Qt: QString and
> > QStringView do actually produce the same hash, but that's not true between
> > QString and QLatin1String. So looking up a QString key (which is
> > implicitly
> > constructible from QLatin1String) requires constructing a QString first.
>
> According to cppreference, this is guaranteed:
>
> "These hashes equal the hashes of corresponding std::basic_string
> classes: If S is one of the standard basic_string types, SV is the
> corresponding string view type, and s is an object of type S, then
> std::hash<S>()(s) == std::hash<SV>()(SV(s))."
>
> I think it would seriously be wrong if it wouldn't be that way.

Ok, good.

It's not difficult to implement so that the hashes are the same, because the
data that they back are completely the same.

That is not the case between QLatin1String and QString, even though the
difference is all zero bits.
It is loading more messages.
0 new messages