簡體   English   中英

無法為 std::variant 使用重載的 operator<<() 流式傳輸 std::endl

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM