无法使用 std::variant 的重载运算符 <<() 流式传输 std::endl

Can't stream std::endl with overloaded operator<<() for std::variant

描述了如何流式传输独立 std::variant。但是,当 std::variant 存储在 std::unordered_map.

中时,它似乎不起作用

以下example

#include <iostream>
#include <string>
#include <variant>
#include <complex>
#include <unordered_map>

// 
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,
                     ~~~~~~~~~~~~~~~~~~^~~

为什么会这样?怎么可能修复它?

问题是 std::endl 但我很困惑为什么你的重载比 std::basic_ostream::operator<<, see godbolt live example:

的重载更匹配
<source>:29:12: note: in instantiation of template class 'std::variant<>' requested here
        << std::endl;
           ^

并删除 std::endl 确实解决了问题,请参阅 live on Wandbox

正如 alfC 指出改变您的操作员以禁止空变体确实解决了这个问题,see it live:

template<typename T, typename... Ts>
std::ostream& operator<<(std::ostream& os, const std::variant<T, Ts...>& v)

出于某种原因,您的代码(在我看来是正确的)试图在 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::variantstd::variant<> 没有 单参数构造函数进行特化,这个问题就不会发生在第一名。查看 https://godbolt.org/z/VGih_4 中的第一行以及它是如何工作的。

namespace std{
   template<> struct variant<>{ ... no single-argument constructor, optionally add static assert code ... };
}

我这样做只是为了说明这一点,我并不一定推荐这样做。

[temp.arg.explicit]/3中,我们有这句神奇的句子:

A trailing template parameter pack not otherwise deduced will be deduced to an empty sequence of template arguments.

这是什么意思?什么是尾随模板参数包?没有其他推论是什么意思?这些都是没有真正答案的好问题。但这会产生非常有趣的后果。考虑:

template <typename... Ts> void f(std::tuple<Ts...>);
f({}); // ok??

这是...格式正确的。我们无法推断 Ts... 所以我们推断它为空。这给我们留下了 std::tuple<>,这是一个完全有效的类型——并且是一个甚至可以用 {} 实例化的完全有效的类型。所以这个编译!

那么当我们从空参数包中推断出的东西不是有效类型时会发生什么?这里是 an example:

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<>,这甚至不可能,所以我们没有问题。