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