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

时间:2018-10-17 00:15:54

标签: c++ c++17 variant

This answer描述了如何流式传输独立的std::variant。但是,当std::variant存储在std::unordered_map中时,它似乎不起作用。

以下example

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

为什么会发生?如何解决?

3 个答案:

答案 0 :(得分:32)

[temp.arg.explicit]/3中,我们有一个惊人的句子:

  

否则未推断出的尾随模板参数包将被推断为模板参数的空序列。

这是什么意思?什么是尾随模板参数包?不以其他方式推论是什么意思?这些都是好的问题,实际上并没有答案。但这会产生非常有趣的结果。考虑:

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<>是无效的类型!我们甚至没有试图找出无法从Y<>构建std::endl的情况-我们已经已经失败了

variant的情况基本上相同,因为variant<>不是有效的类型。

简单的解决方法是将功能模板从variant<Ts...>更改为variant<T, Ts...>。这不再可以归结为variant<>,这甚至不可能,因此我们没有问题。

答案 1 :(得分:7)

由于某种原因,您的代码(对我来说似乎正确)试图在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 ... };
}

我这样做只是为了说明要点,我不必推荐这样做。

答案 2 :(得分:5)

问题是std::endl,但令我感到困惑的是,为什么您的重载比std::basic_ostream::operator<<中的重载更好,请参见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)