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

Dev*_*ull 23 c++ variant c++17

这个答案描述了如何流式传输独立的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;
}
Run Code Online (Sandbox Code Playgroud)

无法编译:

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,
                     ~~~~~~~~~~~~~~~~~~^~~
Run Code Online (Sandbox Code Playgroud)

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

Bar*_*rry 32

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

未以其他方式推导出的尾随模板参数包将被推导为空的模板参数序列.

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

template <typename... Ts> void f(std::tuple<Ts...>);
f({}); // ok??
Run Code Online (Sandbox Code Playgroud)

这是......良好的形式.我们无法推断,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;
}
Run Code Online (Sandbox Code Playgroud)

operator<<是一个潜在的候选人,但扣除失败......或者看起来如此.直到我们Ts...变成空洞.但是Y<>类型无效!我们甚至不试图找出我们不能构建一个Y<>std::endl-我们已经失败了.

这基本上与您的情况相同variant,因为variant<>它不是有效类型.

简单的解决方法是简单地将功能模板从a更改variant<Ts...>为a variant<T, Ts...>.这不能再推断variant<>,这甚至不是可能的事情,所以我们没有问题.

  • 或者`,std :: enable_if_t <0 <sizeof ...(Ts),bool> = true>`SFINAE.因为输入"T0,Ts ......"很烦人.;) (2认同)
  • @Shafik因为`endl`是一个函数模板.所以,就像`{}`例子一样,它不是一个类型的东西.所以演绎失败了,我们又回到了空的"Ts ......"我非常同意这是一个令人惊讶的陷阱. (2认同)
  • @Barry我确定这是一个编译器错误,请参阅我的回答.我不知道标准是否可以更清楚. (2认同)

alf*_*lfC 7

出于某种原因,你的代码(看起来对我来说正确)试图std::variant<>在clang和gcc中实例化(空替换).

我发现的解决方法是为特定的非空变体创建模板.因为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;
}
Run Code Online (Sandbox Code Playgroud)

通过此更改,您的代码适合我.


我还想到如果没有单参数构造函数std::variant的专业化,这个问题就不会发生在第一位.请参阅https://godbolt.org/z/VGih_4中的第一行以及它是如何工作的.std::variant<>

namespace std{
   template<> struct variant<>{ ... no single-argument constructor, optionally add static assert code ... };
}
Run Code Online (Sandbox Code Playgroud)

我这样做只是为了说明这一点,我没有必要建议这样做.


Sha*_*our 5

问题是,std::endl但我很困惑为什么你的重载是比std :: basic_ostream :: operator <<更好的匹配,请参阅godbolt实例:

<source>:29:12: note: in instantiation of template class 'std::variant<>' requested here
        << std::endl;
           ^
Run Code Online (Sandbox Code Playgroud)

并删除std::endl确实解决了问题,在Wandbox上看到它.

正如alfC指出改变你的运算符以禁止空变量确实解决了问题,请看到它的实时:

template<typename T, typename... Ts>
std::ostream& operator<<(std::ostream& os, const std::variant<T, Ts...>& v)
Run Code Online (Sandbox Code Playgroud)