首页 > 解决方案 > 对 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 数组文字不会在它运行时将其转换为布尔值!忽略带字符的运算符重载:

main() {
    Literal myLiteral;
    myLiteral.value = "Hello World";
    std::cout << myLiteral << std::endl;
}

标签: c++variantvisitor-pattern

解决方案


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

如果同时存在范围内的每个from Types...的虚构函数的重载,则确定T_j将由重载决议为表达式选择的替代类型,但以下情况除外:F(std::forward<T>(t))F(T_i)T_i

  • 仅当声明对某些发明变量有效F(T_i)时才考虑重载;T_i x[] = { std::forward<T>(t) };x

  • If T_iis (possible cv-qualified) boolF(T_i)仅考虑 if std:remove_cvref_t<T>is also bool

最后一个条款就是针对这种情况的。因为很多东西都可以转换为bool,但我们通常不打算进行这种转换,所以该子句会导致通常不会被选中的转换序列被选中(char const*tobool是标准转换,但 tostd::string是“用户定义的”,即通常被认为是“更糟”)。你的代码应该设置value为它的std::string替代,但是你的库的实现std::variant被破坏了。可能已经打开了一张问题票,但如果没有,这是打开一张的理由。如果你被你的库困住了,明确地将文字标记为 astd::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 ^^^^^

推荐阅读