2

Consider a piece of generic C++ code which outputs to a stream the values of its arguments in case they are not equal:

#define LOG_IF_NE(a, b) if(a != b) { \
    std::cerr << "Failed because (" << ##a << "=" << (a) << \
        ") != (" << ##b << "=" << (b) << ")"; \
}

This is just an example, the real code throws an exception after writing message to a string stream. This works fine for 2 integers, 2 pointers, etc. for what stream operator << is defined.

int g_b;
int f(int a)
{
    LOG_IF_NE(a, g_b);
    // implementation follows
}

A problem happens when one of the arguments to LOG_IF_NE is nullptr: MSVC++2013 compiler gives error C2593: 'operator <<' is ambiguous.

int *pA;
int g()
{
    LOG_IF_NE(pA, nullptr);
}

The problem happens because nullptr has a special type and operator << is not defined in the STL for that type. An answer at https://stackoverflow.com/a/21772973/1915854 suggests to define operator << for std::nullptr_t

//cerr is of type std::ostream, and nullptr is of type std::nullptr_t
std::ostream& operator << (std::ostream& os, std::nullptr_t)
{
    return os << "nullptr"; //whatever you want nullptr to show up as in the console
}

Is it the right way to solve the problem? Isn't it a bug in C++11/STL that operator<< is not defined for nullptr_t? Is a fix expected in C++14/17? Or was it done on purpose (therefore one's private definition of operator<< could have a pitfall)?

Community
  • 1
  • 1
Serge Rogatch
  • 11,119
  • 4
  • 58
  • 117

2 Answers2

3

This is LWG #2221, which proposes:

The obvious library solution is to add a nullptr_toverload, which would be defined something like

template<class C, class T>
basic_ostream<C, T>& operator<<(basic_ostream<C, T>& os, nullptr_t) 
{ 
  return os << (void*) nullptr; 
}

We might also consider addressing this at a core level: add a special-case language rule that addresses all cases where you write f(nullptr) and f is overloaded on multiple pointer types. (Perhaps a tiebreaker saying that void* is preferred in such cases.)

It isn't in C++14, I don't know whether or not it will make it into C++17. It is a very easy problem to fix yourself, so it's not particularly high priority as far as standards changes go. As you said yourself in the question - it's just a 3 line function.

Barry
  • 247,587
  • 26
  • 487
  • 819
1

I think this may have been intentional for the same reason that nullptr is it's own value. To silently accept it in this situation could be seen as a potential violation of preconditions and invariants.

A nullptr is a value to initialize a pointer to in order to detect that it hasn't been initialized. So by that logic, actually using it anyway should be explicit and documented to prevent misuse and potential security holes. Simply overloading the operator to print it out anyway wouldn't provide this.

It makes a lot more sense in a debug environment (where your macro would call home) to use asserts with conditional compilation if you're checking invariants and program logic with well defined outputs for about everything else.

This basically comes down to a design point: Handle errors where they can be best recovered from. Your macro tests for inequality, but if it discovers a nullptr it doesn't make sense for that macro to handle that error, so falling back on exception handling makes more sense so the problem can be thrown somewhere that can handle and recover from a nullptr. Otherwise you're allowing the program to be in an inconsistent or insecure state.

EDIT: Just saw that you're actually using exception handling. A nested try/catch would probably be optimal because you could catch both errors where they can be handled.

drognisep
  • 449
  • 5
  • 15
  • If I just use 'assert(pA != nullptr)' and it fails, it wouldn't print the values of the variables. To get the values of the variables in the logs and std::exception what() method, a `stringstream` is used (instead of cerr, the latter was just to provide a minimal code example) and a class derived from std::exception, initialized with the message that results from multiple tokens stored in `stringstream` using operator<< . That is why it is valuable for the code to compile when one of the arguments is nullptr. – Serge Rogatch Jul 01 '15 at 18:15
  • That makes more sense since the functionality is used in error code. It just seems IMHO that it opens the door to errors and potential attack vectors. – drognisep Jul 02 '15 at 01:41