对 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<Types...>::operator=(T&& t)
's cppreference page,它
确定替代类型
T_j
,如果同时在作用域内的每个T_i
的类型... ,除了:仅当声明
T_i x[] = std::forward<T>(t) ;
对某些发明变量x
有效时才考虑重载F(T_i)
;如果
T_i
是(可能是 cv 限定的)bool
,则仅当std:remove_cvref_t<T>
也是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
声明与定义不匹配。实际上,它不应该是friend
ed,因为它不需要访问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 的不完整类型