Defining the behavior of std::string(nullptr)

2,185 views
Skip to first unread message

Jim Porter

unread,
Sep 8, 2014, 3:06:13 PM9/8/14
to std-pr...@isocpp.org
Does it make sense to define the behavior of std::string(nullptr)?
Currently, this would call the std::string(const char *s) constructor,
but it violates the precondition that s has at least traits::length(s) +
1 elements.

Since you can guarantee at compile-time that this isn't true for
nullptr, I think it would make sense to raise an error during
compilation. I imagine an implementation is already free to do this, but
standardizing it would eliminate a source of potential segfaults.

- Jim

David Krauss

unread,
Sep 8, 2014, 10:20:26 PM9/8/14
to std-pr...@isocpp.org
On 2014–09–09, at 3:05 AM, Jim Porter <jvp...@g.rit.edu> wrote:

Does it make sense to define the behavior of std::string(nullptr)? Currently, this would call the std::string(const char *s) constructor, but it violates the precondition that s has at least traits::length(s) + 1 elements.

Since you can guarantee at compile-time that this isn't true for nullptr, I think it would make sense to raise an error during compilation. I imagine an implementation is already free to do this, but standardizing it would eliminate a source of potential segfaults.

Checking pointer parameters of every function against nullptr just makes more work for lightweight processors. If the library wants to eliminate segfaults, it can throw std::invalid_argument, or do anything else, because it’s UB.

Jim Porter

unread,
Sep 8, 2014, 10:49:12 PM9/8/14
to std-pr...@isocpp.org
On 9/8/2014 9:20 PM, David Krauss wrote:
>
> On 2014–09–09, at 3:05 AM, Jim Porter <jvp...@g.rit.edu
What I meant was to add a deleted std::basic_string(std::nullptr_t)
constructor. That wouldn't affect runtime performance at all, and would
just make a small subset of programs with UB fail to compile. Granted,
it's an edge case, and this overload wouldn't catch problems where you
had a null char*, but it's easy to add a deleted constructor like this,
and it prevents *some* UB from compiling.

The main thing I'm trying to prevent is implicit conversions from
nullptr to a std::string. Obviously, std::string(const char*) should
allow for implicit conversions in general, but that makes it possible to
pass nullptr to a function that expects a std::string and now you have
UB (probably a segfault) instead of just a compilation error. Adding a
deleted constructor taking a std::nullptr_t would close that admittedly
small hole at no runtime cost, and only a small compile-time cost.

- Jim

David Krauss

unread,
Sep 8, 2014, 11:09:08 PM9/8/14
to std-pr...@isocpp.org

On 2014–09–09, at 10:48 AM, Jim Porter <jvp...@g.rit.edu> wrote:

> The main thing I'm trying to prevent is implicit conversions from nullptr to a std::string. Obviously, std::string(const char*) should allow for implicit conversions in general, but that makes it possible to pass nullptr to a function that expects a std::string and now you have UB (probably a segfault) instead of just a compilation error. Adding a deleted constructor taking a std::nullptr_t would close that admittedly small hole at no runtime cost, and only a small compile-time cost.

Oh, OK. That sounds like a library defect. Try asking Alisdair.

Jonathan Coe

unread,
Sep 9, 2014, 3:19:16 AM9/9/14
to std-pr...@isocpp.org
Deleted constructor sounds like a fine idea to me. Always good to catch things at compile time.

> On 9 Sep 2014, at 04:08, David Krauss <pot...@gmail.com> wrote:
>
>
>> On 2014-09-09, at 10:48 AM, Jim Porter <jvp...@g.rit.edu> wrote:
>>
>> The main thing I'm trying to prevent is implicit conversions from nullptr to a std::string. Obviously, std::string(const char*) should allow for implicit conversions in general, but that makes it possible to pass nullptr to a function that expects a std::string and now you have UB (probably a segfault) instead of just a compilation error. Adding a deleted constructor taking a std::nullptr_t would close that admittedly small hole at no runtime cost, and only a small compile-time cost.
>
> Oh, OK. That sounds like a library defect. Try asking Alisdair.
>
> --
>
> ---
> You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
> To post to this group, send email to std-pr...@isocpp.org.
> Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

David Rodríguez Ibeas

unread,
Sep 10, 2014, 9:25:26 AM9/10/14
to std-pr...@isocpp.org
While I am not against the idea, I doubt that this would catch a whole lot of issues. In particular, any null pointer that is not known at compile time to be a null pointer, and even null pointers of a type different than nullptr_t would not be detected. Additionally it raises the question of where to stop, should we also add overloads that take pointers everywhere? What about interfaces that take pair of iterators?

A slightly more useful approach would be to add an attribute [[not_null]] to the arguments of the different functions. It would serve as explicit documentation that null is not accepted for the programmer, and the compiler would be able to produce diagnostics where either nullptr or a null pointer of a different type is passed to the function.  It would still raise the question of how the compiler/optimizer would be able to use that information, i.e. is it undefined behavior to call such a function with a null pointer? can the optimizer use this information to drop code? For example:

void f(const char *msg) {
    std::string s(msg);
    if (!msg) {
        std::cout << "Hi there\n"; // could this be dropped by the optimizer?
   }
}

But I am getting ahead of myself, let's start simple: would such an attribute in the standard and its application to the library be useful?

    David

j...@chez-jim.net

unread,
Sep 10, 2014, 1:06:22 PM9/10/14
to std-pr...@isocpp.org
On Monday, 8 September 2014 21:06:13 UTC+2, Jim Porter wrote:
Does it make sense to define the behavior of std::string(nullptr)?

I was thinking about this recently, and I completely agree. Constructing or assigning from nullptr currently results in undefined behaviour, so why not just make it a compilation error?

Jim Porter

unread,
Sep 10, 2014, 1:46:00 PM9/10/14
to std-pr...@isocpp.org
On 9/10/2014 8:25 AM, David Rodríguez Ibeas wrote:
> While I am not against the idea, I doubt that this would catch a whole
> lot of issues. In particular, any null pointer that is not known at
> compile time to be a null pointer, and even null pointers of a type
> different than nullptr_t would not be detected. Additionally it raises
> the question of where to stop, should we also add overloads that take
> pointers everywhere? What about interfaces that take pair of iterators?

I think we'd only need to do this in places where a std::nullptr_t could
be implicitly converted to another (non-pointer) type in a way that
causes UB; those are the places that have caused the most confusion for
me. This means that any ordinary function could remain unchanged, but
classes might need a new overload.

> A slightly more useful approach would be to add an attribute
> [[not_null]] to the arguments of the different functions. It would serve
> as explicit documentation that null is not accepted for the programmer,
> and the compiler would be able to produce diagnostics where either
> nullptr or a null pointer of a different type is passed to the function.

This would probably be a better long-term solution, though. I imagine it
would help quite a bit with static analysis as well. I'm not sure what
should happen if [[not_null]] were applied to a non-pointer type, though
(especially when the type is templated and may or may not be a pointer).
Ideally, [[not_null]] would apply to iterators and smart pointers as
well, but that's probably hard to do.

As long as [[not_null]] were specified to emit a compilation error when
passing nullptr, I'd be happy.

- Jim

Thiago Macieira

unread,
Sep 10, 2014, 4:59:01 PM9/10/14
to std-pr...@isocpp.org
On Monday 08 September 2014 21:48:43 Jim Porter wrote:
> The main thing I'm trying to prevent is implicit conversions from
> nullptr to a std::string. Obviously, std::string(const char*) should
> allow for implicit conversions in general, but that makes it possible to
> pass nullptr to a function that expects a std::string and now you have
> UB (probably a segfault) instead of just a compilation error.

Why are you doing that in the first place? Sounds like this is what you want to
catch as the mistake.

Your proposal wouldn't deal with passing a null const char* pointer:

extern void f(std::string);
const char *ptr = nullptr;
f(nullptr);
f(ptr);

Why should the two calls behave differently?

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Thiago Macieira

unread,
Sep 10, 2014, 5:00:43 PM9/10/14
to std-pr...@isocpp.org
On Wednesday 10 September 2014 09:25:25 David Rodríguez Ibeas wrote:
> A slightly more useful approach would be to add an attribute [[not_null]]

I like this proposal better. It's currently allowed by a GNU extension:

[[gnu:nonnull(1)]] void f(const char *msg);

David Krauss

unread,
Sep 10, 2014, 6:04:02 PM9/10/14
to std-pr...@isocpp.org
On 2014–09–10, at 9:25 PM, David Rodríguez Ibeas <dib...@ieee.org> wrote:

While I am not against the idea, I doubt that this would catch a whole lot of issues.

I think it might. The problem is function parameters that have migrated from char * to std::string. Client code which passes NULL compiles the same either way.

Pathological C users who reflexively pass NULL to opt-out of a function feature might as well be stopped at compile time.

In particular, any null pointer that is not known at compile time to be a null pointer, and even null pointers of a type different than nullptr_t would not be detected. Additionally it raises the question of where to stop, should we also add overloads that take pointers everywhere? What about interfaces that take pair of iterators?

It sounds reasonable to add a nullptr_t overload to every library function that accepts a C string that could plausibly be considered optional.

A pair of null pointers actually forms a valid empty range.

A slightly more useful approach would be to add an attribute [[not_null]] to the arguments of the different functions.

I don’t think that attribute would make sense for a std::string parameter. The debug mode standard library might include a sanity check in std::string::string(char const*), and that sanity check could be generated by a [[not_null]] facility. The nullptr_t overload would prevent reaching that constructor, so that approach would be complementary.

Thiago Macieira

unread,
Sep 10, 2014, 6:11:35 PM9/10/14
to std-pr...@isocpp.org
On Thursday 11 September 2014 06:03:48 David Krauss wrote:
> > In particular, any null pointer that is not known at compile time to be a
> > null pointer, and even null pointers of a type different than nullptr_t
> > would not be detected. Additionally it raises the question of where to
> > stop, should we also add overloads that take pointers everywhere? What
> > about interfaces that take pair of iterators?
> It sounds reasonable to add a nullptr_t overload to every library function
> that accepts a C string that could plausibly be considered optional.

Why stop with nullptr_t? Add an overload that takes const void* so we catch
any other types of pointers.

David Krauss

unread,
Sep 10, 2014, 6:15:59 PM9/10/14
to std-pr...@isocpp.org
On 2014–09–11, at 6:11 AM, Thiago Macieira <thi...@macieira.org> wrote:

Why stop with nullptr_t? Add an overload that takes const void* so we catch
any other types of pointers.

Other pointers don’t implicitly convert to char *. (There is such a nonconforming extension which GCC applies in C mode, but not C++.)

Anyway, passing NULL as a string is fairly common. You might say it only *was* common in neanderthal days, but bad habits die slow.

Nevin Liber

unread,
Sep 10, 2014, 7:50:02 PM9/10/14
to std-pr...@isocpp.org
On 10 September 2014 15:03, David Krauss <pot...@gmail.com> wrote:
Pathological C users who reflexively pass NULL to opt-out of a function feature might as well be stopped at compile time.

if NULL is defined as "0" (as it still tends to be on many systems, because changing it tends to break existing [but dubious] code), will the nullptr_t constructor be either a better or ambiguous match?
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

David Krauss

unread,
Sep 10, 2014, 7:53:36 PM9/10/14
to std-pr...@isocpp.org
On 2014–09–11, at 7:49 AM, Nevin Liber <ne...@eviloverlord.com> wrote:

On 10 September 2014 15:03, David Krauss <pot...@gmail.com> wrote:
Pathological C users who reflexively pass NULL to opt-out of a function feature might as well be stopped at compile time.

if NULL is defined as "0" (as it still tends to be on many systems, because changing it tends to break existing [but dubious] code), will the nullptr_t constructor be either a better or ambiguous match?

Does it matter? One is diagnosed as selection of a deleted overload and the other is diagnosed as no selection at all. In both cases the diagnostics will (in practice) mention the nullptr overload.

Jim Porter

unread,
Sep 10, 2014, 9:46:07 PM9/10/14
to std-pr...@isocpp.org
On 9/10/2014 3:58 PM, Thiago Macieira wrote:
> On Monday 08 September 2014 21:48:43 Jim Porter wrote:
>> The main thing I'm trying to prevent is implicit conversions from
>> nullptr to a std::string. Obviously, std::string(const char*) should
>> allow for implicit conversions in general, but that makes it possible to
>> pass nullptr to a function that expects a std::string and now you have
>> UB (probably a segfault) instead of just a compilation error.
>
> Why are you doing that in the first place? Sounds like this is what you want to
> catch as the mistake.
>
> Your proposal wouldn't deal with passing a null const char* pointer:
>
> extern void f(std::string);
> const char *ptr = nullptr;
> f(nullptr);
> f(ptr);
>
> Why should the two calls behave differently?

Mainly because one is easy to catch with the existing language rules
(nullptr), and one is hard to (ptr). :)

- Jim


Christopher Jefferson

unread,
Sep 15, 2014, 8:27:36 AM9/15/14
to std-pr...@isocpp.org
The following program is (I believe) valid C++11, and would break if
you added a deleted nullptr constructor to std::string.

#include <iostream>
#include <string>
#include <ostream>

using namespace std;

template<typename ptr>
string make_string(ptr p)
{
if(p)
return string(p);
else
return string("<NULL>");
}

int main(void)
{ std::cout << make_string("hello") << make_string((char*)0) <<
make_string(nullptr) << "\n"; }

David Krauss

unread,
Sep 15, 2014, 9:00:44 AM9/15/14
to std-pr...@isocpp.org

On 2014–09–15, at 8:27 PM, Christopher Jefferson <ch...@bubblescope.net> wrote:

> The following program is (I believe) valid C++11,

But do you believe it to be reasonable?

It’s not in outer space, but it is a mishmash of generic and old-school.

Christopher Jefferson

unread,
Sep 15, 2014, 9:39:31 AM9/15/14
to std-pr...@isocpp.org
On 15 September 2014 14:00, David Krauss <pot...@gmail.com> wrote:
It's certainly reasonable to have a wrapper function for constructing
a std::string from a pointer which checks if the pointer is NULL, as
constructing a string from NULL is undefined behaviour. Is it valid to
write it in this way? It is certainly strange code but (in general) we
try to avoid breaking correct programs.

Also, this only partially patches up the problem and not in a way I
would like to rely on (as if you ever turn the nullptr into a
(char*)0, then you move from doesn't compile to UB). In general, I
would personally prefer to keep null pointers and nullptr acting in
the same way.

If we just wanted to make std::string((char*)0) defined behaviour,
that would seem to be easier. I know that g++ makes this code defined
(and makes an empty string). Has anyone checked what other compilers
do?

Chris
Reply all
Reply to author
Forward
0 new messages