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

Posted

技术标签:

【中文标题】对 std::variant 中保存的类型调用 << 运算符?【英文标题】:Calling << operator on types held in a std::variant? 【发布时间】:2020-04-19 02:10:56 【问题描述】:

我有一个这样的结构:

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

但是在我的 main 函数中,传递一个 char 数组文字并不会在它运行时将其转换为 bool!忽略带字符的运算符重载:

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

【问题讨论】:

【参考方案1】:

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

确定替代类型T_j,如果同时在作用域内的每个T_i 的类型... ,除了:

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

如果 T_i 是(可能是 cv 限定的)bool,则仅当 std:remove_cvref_t&lt;T&gt; 也是 bool 时才考虑 F(T_i)

最后一个子句就是针对这种情况的。因为很多东西都可以转换为bool,但我们通常不打算进行这种转换,所以该子句会导致通常不会被选中的转换序列被选中(char const*bool 是标准转换,但std::string 是“用户定义的”,通常被认为是“更糟糕的”)。您的代码应该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 声明与定义不匹配。实际上,它不应该是friended,因为它不需要访问private 成员。

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

【讨论】:

FWIW,我似乎记得 bool 异常稍后会在另一篇论文中出现,因此很可能该实现还没有在其旧支持之上实现该论文。 (而且由于它改变了现有代码的行为,我可以看出这可能是一个故意延迟的决定。)(啊,它在页面上链接:open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0608r3.html @chris 感谢您的发现。我检查了 eel.is 以仔细检查标准所说的内容,但显然还没有。 我正在使用 clang++。我的构建任务是 clang++ -g *.cpp -o main -std=c++17。 clang++ --version 给苹果 clang 11 (clang-1100.0.33.17) 手动将字符转换为字符串确实有效,但我想看看是否可以修复我当前的实现。我会检查你的 lambda 函数方法,看看它是否有效 我想 Apple clang 有点落后。我正在 godbolt.org 上测试实际的 LLVM Clang 10.0.0(使用-stdlib=libc++,因为出于某种被上帝遗忘的原因,它默认为 GNU libstdc++)。

以上是关于对 std::variant 中保存的类型调用 << 运算符?的主要内容,如果未能解决你的问题,请参考以下文章

std::visit 无法推断 std::variant 的类型

使用类作为数据类型时如何在 std::variant 中存储值?

具有 std::map 和 std::variant 的不完整类型

如何在编译时捕获 std::variant 持有错误类型?

在构造 std::variant 时禁用从指针类型到 bool 的隐式转换的最佳方法是啥?

使用 std::variant 的静态多态性