[英]Can't stream std::endl with overloaded operator<<() for std::variant
此答案描述了如何流式传输独立的std::variant
。 但是,当std::variant
存储在std::unordered_map
时,它似乎不起作用。
下面的例子:
#include <iostream>
#include <string>
#include <variant>
#include <complex>
#include <unordered_map>
// https://stackoverflow.com/a/46893057/8414561
template<typename... Ts>
std::ostream& operator<<(std::ostream& os, const std::variant<Ts...>& v)
{
std::visit([&os](auto&& arg) {
os << arg;
}, v);
return os;
}
int main()
{
using namespace std::complex_literals;
std::unordered_map<int, std::variant<int, std::string, double, std::complex<double>>> map{
{0, 4},
{1, "hello"},
{2, 3.14},
{3, 2. + 3i}
};
for (const auto& [key, value] : map)
std::cout << key << "=" << value << std::endl;
}
无法编译:
In file included from main.cpp:3:
/usr/local/include/c++/8.1.0/variant: In instantiation of 'constexpr const bool std::__detail::__variant::_Traits<>::_S_default_ctor':
/usr/local/include/c++/8.1.0/variant:1038:11: required from 'class std::variant<>'
main.cpp:27:50: required from here
/usr/local/include/c++/8.1.0/variant:300:4: error: invalid use of incomplete type 'struct std::__detail::__variant::_Nth_type<0>'
is_default_constructible_v<typename _Nth_type<0, _Types...>::type>;
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/include/c++/8.1.0/variant:58:12: note: declaration of 'struct std::__detail::__variant::_Nth_type<0>'
struct _Nth_type;
^~~~~~~~~
/usr/local/include/c++/8.1.0/variant: In instantiation of 'class std::variant<>':
main.cpp:27:50: required from here
/usr/local/include/c++/8.1.0/variant:1051:39: error: static assertion failed: variant must have at least one alternative
static_assert(sizeof...(_Types) > 0,
~~~~~~~~~~~~~~~~~~^~~
为什么会发生? 怎么可能修复它?
在[temp.arg.explicit]/3 中,我们有这个惊人的句子:
未以其他方式推导的尾随模板参数包将推导为模板参数的空序列。
这是什么意思? 什么是尾随模板参数包? 否则不推导出是什么意思? 这些都是没有真正答案的好问题。 但这有非常有趣的后果。 考虑:
template <typename... Ts> void f(std::tuple<Ts...>);
f({}); // ok??
这是...格式良好。 我们不能推断出Ts...
所以我们推断它为空。 这给我们留下了std::tuple<>
,它是一个完全有效的类型 - 一个完全有效的类型,甚至可以用{}
实例化。 所以这编译!
那么当我们从我们想象出来的空参数包中推导出来的东西不是有效类型时会发生什么? 这是一个例子:
template <class... Ts>
struct Y
{
static_assert(sizeof...(Ts)>0, "!");
};
template <class... Ts>
std::ostream& operator<<(std::ostream& os, Y<Ts...> const& )
{
return os << std::endl;
}
operator<<
是一个潜在的候选者,但推论失败了......或者看起来是这样。 直到我们把Ts...
想象成空的。 但是Y<>
是一个无效类型! 我们甚至没有试图找出我们无法从std::endl
构造Y<>
- 我们已经失败了。
这与您遇到的variant
情况基本相同,因为variant<>
不是有效类型。
简单的解决方法是简单地将函数模板从variant<Ts...>
更改为variant<T, Ts...>
。 这不能再推论到variant<>
,这甚至是不可能的事情,所以我们没有问题。
出于某种原因,您的代码(在我看来是正确的)正在尝试在 clang 和 gcc 中实例化std::variant<>
(空替代)。
我发现的解决方法是为特定的非空变体制作模板。 由于std::variant
无论如何都不能为空,我认为为非空变体编写泛型函数通常是好的。
template<typename T, typename... Ts>
std::ostream& operator<<(std::ostream& os, const std::variant<T, Ts...>& v)
{
std::visit([&os](auto&& arg) {
os << arg;
}, v);
return os;
}
通过此更改,您的代码对我有用。
我还发现,如果std::variant
有一个没有单参数构造函数的std::variant<>
,这个问题就不会发生。 请参阅https://godbolt.org/z/VGih_4 中的第一行以及它是如何工作的。
namespace std{
template<> struct variant<>{ ... no single-argument constructor, optionally add static assert code ... };
}
我这样做只是为了说明这一点,我并不一定建议这样做。
问题是std::endl
但我很困惑为什么你的重载比std::basic_ostream::operator<<的重载更匹配,请参见Godbolt 现场示例:
<source>:29:12: note: in instantiation of template class 'std::variant<>' requested here
<< std::endl;
^
并删除std::endl
确实解决了问题,请在 Wandbox 上实时查看。
正如 alfC 指出的那样,更改您的操作员以禁止空变体确实可以解决问题,请实时查看:
template<typename T, typename... Ts>
std::ostream& operator<<(std::ostream& os, const std::variant<T, Ts...>& v)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.