如何流式传输 std::variant<...,...>?

13

我的 std::variant 包含可流输出的类型:

std::variant<int, std::string> a, b;
a = 1;
b = "hi";
std::cout << a << b << std::endl;
使用g++7和参数-std=c++1z编译会出现编译时错误。
摘要:
test.cpp: In function 'int main(int, char**)':
test.cpp:10:13: error: no match for 'operator<<' (operand types are 'std::ostream {aka std::basic_ostream<char>}' and 'std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >')
   std::cout << a << b << std::endl;
   ~~~~~~~~~~^~~~

看起来一个std::variant<int, std::string>不支持流操作。我该如何实现直接将variant流输出到输出流中?

期望输出:

1hi

6
类似这样的代码:std::visit([](const auto& v) { std::cout << v; }, a); - Igor Tandetnik
1
Boost.Variant 支持插入运算符,但我不清楚为什么这个运算符在 std::variant 中被省略了。http://www.boost.org/doc/libs/1_65_1/doc/html/boost/operator_idp789915280.html - GManNickG
3个回答

17
这也可以流式传输嵌套变量。
template<class T>
struct streamer {
    const T& val;
};
template<class T> streamer(T) -> streamer<T>;

template<class T>
std::ostream& operator<<(std::ostream& os, streamer<T> s) {
    os << s.val;
    return os;
}

template<class... Ts>
std::ostream& operator<<(std::ostream& os, streamer<std::variant<Ts...>> sv) {
   std::visit([&os](const auto& v) { os << streamer{v}; }, sv.val);
   return os;
}

用法:

std::cout << streamer{a} << streamer{b} << '\n';

2
这个为什么不在标准里面,有什么原因吗? - Claas Bontus
1
@ClaasBontus 常见的答案:这并没有被提出。 - sehe
2
能否有人澄清这行代码: template<class T> streamer(T) -> streamer<T>; - LeDYoM
3
它允许流类的模板参数推导,因此您不需要编写 streamer<lots-of-stuff>{a}。请参考这篇文章中的用户自定义推导指南章节。 - nnovich-OK
请注意,在C++20中,对于聚合类模板,不需要使用扣除指南,因为它们会被隐式生成。 - Davis Herring

6

我不确定这是否是一个好主意,但是我认为你可以为std::variant定义一个operator<<()

仅仅是为了好玩,我实现了下面例子中的代码(我认为它可以再简化一些):

#include <variant>
#include <iostream>

template <std::size_t I, typename T0, typename ... Ts>
std::enable_if_t<(I == 1U+sizeof...(Ts)), std::ostream &>
   streamV (std::ostream & s, std::variant<T0, Ts...> const &)
 { return s; }

template <std::size_t I, typename T0, typename ... Ts>
std::enable_if_t<(I < 1U+sizeof...(Ts)), std::ostream &>
   streamV (std::ostream & s, std::variant<T0, Ts...> const & v)
 { return I == v.index() ? s << std::get<I>(v) : streamV<I+1U>(s, v); }

template <typename T0, typename ... Ts>
std::ostream & operator<< (std::ostream & s, 
                           std::variant<T0, Ts...> const & v)
 { return streamV<0U>(s, v); }

int main ()
 {
   std::variant<int, std::string> a, b;
   a = 1;
   b = "hi";
   std::cout << a << b << std::endl;
}

-- 编辑 --

另一种编写streamV()辅助函数的方式,不使用T0,Ts...类型,而是使用std::variant_size_v

template <std::size_t I, typename V>
std::enable_if_t<(I == std::variant_size_v<V>), std::ostream &>
   streamV (std::ostream & s, V const &)
 { return s; }

template <std::size_t I, typename V>
std::enable_if_t<(I < std::variant_size_v<V>), std::ostream &>
   streamV (std::ostream & s, V const & v)
 { return I == v.index() ? s << std::get<I>(v) : streamV<I+1U>(s, v); }

--编辑2--

正如T.C.(感谢!)所指出的那样,我只实现了一个比std::visit()不太有效、不太有趣和不太有用的版本 (streamV())。

使用std::visit(),我的示例可以变得更加简单。

#include <variant>
#include <iostream>

template <typename T0, typename ... Ts>
std::ostream & operator<< (std::ostream & s,
                           std::variant<T0, Ts...> const & v)
 { std::visit([&](auto && arg){ s << arg;}, v); return s; }

int main ()
 {
   std::variant<int, std::string> a, b;
   a = 1;
   b = "hi";
   std::cout << a << b << std::endl;
}

我重申一遍:这只是为了好玩,因为我认为定义operator<<()于标准类型不是一个好主意。

我建议采用T.C.提出的解决方案,将变体实例封装到特定类中进行流操作。


4
不要重新实现一个线性复杂度版本的std::visit - T.C.
@T.C. - 是的...我想我又重新发明了轮子 :( - max66

5
注意:以下示例摘自Igor Tandetnik在问题本身上的评论。 std::visit是标准库中的一个函数,可用于此确切目的。
#include <variant>
#include <iostream>

int main() {
    std::variant<int, std::string> value = 42;

    std::visit([](const auto &elem) { std::cout << elem << '\n'; }, value);
}

上面的代码段本质上是一种更加华丽的写法,实际上就是:

...


#include <variant>
#include <iostream>

int main() {
    std::variant<int, std::string> value = 42;

    if(std::holds_alternative<int>(value)) {
      std::cout << std::get<int>(value) << '\n';
    } else {
      std::cout << std::get<std::string>(value) << '\n';
    }
}

不明白为什么人们没有为这个答案点赞。 - sofname

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接