A common optimization scenario

A common problem with the ternary operator involves trying to optimizate an assigned like this:

1
2
3
4
5
6
7
#include <string>
std::string GetPossibleString();
void DoStuff() {
    std::string possible_string = GetPossibleString();
    auto string_to_use = possible_string.empty() ? "Default"
                                                 : possible_string;
}

What is the type of string_to_use? Well, it is of type std::string as there is an implicit conversion from the const char* C-style string "Default" to a std::string.

Now, this might be fine, and expected, but following the latest guidance from the C++ standard library, you may see more and more interfaces flexible enough to take std::string_view, which is a lightweight, readonly view of a “string”, which might be a std::string, or const char*. Ownership of the string is not taken, as the name suggests: it’s just a view of the string.

Then you might say, well, I do not want to incurr the expense of dynamic memory allocation and copying "Default" to a std::string, so, knowing that both possible_string and "Default" are implicitly convertible to a std::string_view, I’ll modify the code as follows:

1
2
3
4
5
6
7
8
#include <string>
#include <string_view>
std::string GetPossibleString();
void DoStuff() {
    std::string possible_string = GetPossibleString();
    std::string_view string_to_use = possible_string.empty() ? "Default"
                                                             : possible_string;
}

However, you’ve now inadvertently entered the world of undefined behavior.

The reason is because the ternary operator doesn’t really care what the type of string_to_use is. It’s going to resolve a common type between the two branches possible involving an implicit conversion.

In this case, there is only one thing it can do: the implicit conversion of "Default" to a std::string. After that, either that temporary string, or possible_string is converted to a std::string_view. Needless to say, a std::string_view pointing at a temporary value is bad, since this will elicit the same undefinied behavior of taking the address of a temporary and using it after the statement the temporary is in.

So, how to fix this? There should be a way to do this because we rightly should be able to assign the possible_string and "Default" to a std::string_view without problem.

Solution

The trick is, you need to explicitly make one of the branches of the ternary operator to be a std::string_view in one of its branches (or both). So this is a possible solution:

1
2
3
4
5
6
7
8
9
#include <string>
#include <string_view>
std::string GetPossibleString();
void DoStuff() {
    std::string possible_string = GetPossibleString();
    std::string_view string_to_use =
        possible_string.empty() ? "Default"
                                : std::string_view(possible_string);
}

The moral of the story is, implicit conversions do not always behave as you expect, particuarly in the presence of resolution in the ternary operator. Be very careful, and when in double, do explicitly conversions.