对 std::variant 中保存的类型调用 << 运算符?

Calling << operator on types held in a std::variant?

我有这样的结构:

// Literal.hpp
struct Literal
{
    std::variant<
        std::nullptr_t,
        std::string,
        double,
        bool
        >
        value;

    friend std::ostream &operator<<(std::ostream &os, Literal &literal);
};

我正在尝试像这样实现 << 运算符:

// 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);
}

我试过像这样实现运算符(注意:我会采用任何优雅的解决方案,它不一定是下面这个实现的解决方案)

// 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;}

但是在我的主函数中,传递一个 char 数组文字并不会在运行时将其转换为 bool!忽略采用 char:

的运算符重载
main() {
    Literal myLiteral;
    myLiteral.value = "Hello World";
    std::cout << myLiteral << std::endl;
}

这是您的标准库中的错误。大概您正在使用 libstc++(GNU C++ 标准库),因为这就是 Godbolt 显示的混乱。如果您使用 libc++(Clang/LLVM 的 C++ 标准库)编译,这将按预期工作。根据std::vector<Types...>::operator=(T&& t)'s cppreference page,它

Determines the alternative type T_j that would be selected by overload resolution for the expression F(std::forward<T>(t)) if there was an overload of imaginary function F(T_i) for every T_i from Types... in scope at the same time, except that:

  • An overload F(T_i) is only considered if the declaration T_i x[] = { std::forward<T>(t) }; is valid for some invented variable x;

  • If T_i is (possibly cv-qualified) bool, F(T_i) is only considered if std:remove_cvref_t<T> is also bool.

最后一个条款就是针对这种情况。因为很多东西都可以转换为 bool,但我们通常不打算进行这种转换,所以该子句会导致选择通常不会被选择的转换序列(char const*bool 是标准转换,但到 std::string 是 "user-defined",通常被认为是 "worse")。您的代码 应该 value 设置为其 std::string 替代项,但是您的库对 std::variant 的实现已损坏。可能已经打开了一张问题单,但如果没有,则可以打开一张。如果你坚持使用你的库,明确地将文字标记为 std::string 应该可行:

literal.value = std::string("Hello World");

对于优雅问题,使用缩写模板 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;
}

此外,您的 friend 声明与定义不符。实际上,它不应该被 friend 编辑,因为它不需要访问 private 成员。

// declaration in header, outside of any class, as a free function
std::ostream &operator<<(std::ostream&, Literal const&);
//                            was missing const ^^^^^