I've got a struct like this:
// Literal.hpp
struct Literal
{
std::variant<
std::nullptr_t,
std::string,
double,
bool
>
value;
friend std::ostream &operator<<(std::ostream &os, Literal &literal);
};
and I'm trying to implement the << operator like this:
// Literal.cpp
Literal::Literal() : value(value) {}
std::ostream &operator<<(std::ostream &os, const Literal &literal)
{
std::visit(/* I don't know what to put here!*/, literal.value);
}
I've tried implementing the operator like this (note: I would take any elegant solution it doesn't have to be a solution to this implementation below)
// In Literal.cpp
std::ostream &operator<<(std::ostream &out, const Literal literal)
{
std::visit(ToString(), literal.value);
return out;
}
struct ToString; // this declaration is in literal.hpp
void ToString::operator()(const std::nullptr_t &literalValue){std::cout << "null";}
void ToString::operator()(const char &literalValue){std::cout << std::string(literalValue);}
void ToString::operator()(const std::string &literalValue){std::cout << literalValue;}
void ToString::operator()(const double &literalValue){std::cout << literalValue;}
void ToString::operator()(const bool &literalValue){std::cout << literalValue;}
But in my main function, passing a char array literal doesn't casts it into a bool when it runs! ignoring the operator overload taking a char:
main() {
Literal myLiteral;
myLiteral.value = "Hello World";
std::cout << myLiteral << std::endl;
}
This is a bug in your standard library. Presumably you're using libstc++ (the GNU C++ standard library), since that's what Godbolt shows as messing up. If you compile with libc++ (Clang/LLVM's C++ standard library), this works as expected. According to std::vector<Types...>::operator=(T&& t)
's cppreference page, it
Determines the alternative type
T_j
that would be selected by overload resolution for the expressionF(std::forward<T>(t))
if there was an overload of imaginary functionF(T_i)
for everyT_i
from Types... in scope at the same time, except that:
An overload
F(T_i)
is only considered if the declarationT_i x[] = { std::forward<T>(t) };
is valid for some invented variablex
;If
T_i
is (possibly cv-qualified)bool
,F(T_i)
is only considered ifstd:remove_cvref_t<T>
is alsobool
.
That last clause is there for this very situation. Because lots of things can convert to bool
, but we don't usually intend this conversion, that clause causes conversion sequences that would not normally be selected to be selected (char const*
to bool
is a standard conversion, but to std::string
is "user-defined", which is normally considered "worse"). Your code should set value
to its std::string
alternative, but your library's implementation of std::variant
is broken. There's probably an issue ticket already opened, but if there isn't, this is grounds to open one. If you're stuck with your library, explicitly marking the literal as a std::string
should work:
literal.value = std::string("Hello World");
For the elegance question, use an abbreviated template lambda.
std::ostream &operator<<(std::ostream &os, Literal const &literal)
{
std::visit([](auto v) { std::cout << v; }, literal.value);
// or
std::visit([](auto const &v) {
// gets template param vvvvvvvvvvvvvvvvvvvvvvvvv w/o being able to name it
if constexpr(std::is_same_v<std::decay_t<decltype(v)>, std::nullptr_t>) {
std::cout << "null";
} else std::cout << v;
}, literal.value);
// only difference is nullptr_t => "nullptr" vs "null"
return std::cout;
}
Also, your friend
declaration doesn't match the definition. Actually, it shouldn't be friend
ed anyway, since it needs no access to private
members.
// declaration in header, outside of any class, as a free function
std::ostream &operator<<(std::ostream&, Literal const&);
// was missing const ^^^^^