Search code examples
c++referencestdstringinitializernullptr

How `const std::string& s = nullptr` works as an optional parameter


To my knowledge a reference cannot be null, but when I run code like this:

#include <iostream>
#include <string>

void test(int i, const std::string& s = nullptr) {
  std::cout << i << " " << s << std::endl;
}

int main() {
  test(1, "test");
  test(2);
}

the optional parameter s can be null, and the code is built. What's more, when test(2) runs, the program throws exceptions instead of printing some random strings.

When I changed s to some basic type like int, it failed to compile, so I think the magic stays inside the string class, but how?

And what's more, how can I check if s is null or not? if I using if(s==nullptr) or if(s.empty()), it fails to compile.


Solution

  • test initialized its argument by using constructor number 5 of std::basic_string<char>:

    basic_string( const CharT* s,
                  const Allocator& alloc = Allocator() );
    

    Since it needs to materialize a temporary (std::string) to bind to that reference. That is because a reference must be bound to an object of a correct type, which std::nullptr_t isn't. And said constructor has a not null constraint on the pointer being passed. Calling test without an explicit argument leads to undefined behavior.

    To be perfectly clear, there is no such thing as a null reference in a well-formed C++ program. A reference must be bound to a valid object. Trying to initialize one with nullptr will only seek out to do a conversion.

    Since a std::string is an object with a well-defined "empty" state, a fixed version can simply pass in a default initialized string:

    void test(int i, const std::string& s = {}); // Empty string by default.
    

    Once the contract violation is fixed, s.empty() should give meaningful results again.