How to stream std::variant<…,…>

不想你离开。 提交于 2020-01-01 09:04:10

问题


My std::variant contains streamable types:

std::variant<int, std::string> a, b;
a = 1;
b = "hi";
std::cout << a << b << std::endl;

Compiling with g++7 with -std=c++1z returns compilation time errors.

An excerpt:

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;
   ~~~~~~~~~~^~~~

Seemingly a std::variant<int, std::string> is not able to stream. How can I achieve that I can directly stream the variant to an output stream?

Expected output:

1hi

回答1:


This streams nested variants too.

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;
}

Use as:

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



回答2:


Not sure it's a good idea but I suppose you could define an operator<<() for std::variant.

Just for fun I've realized the one you can see in the following example (I suppose can be simplified a little)

#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;
}

-- EDIT --

Another way to write the streamV() helper function, without the T0, Ts... types but using 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); }

-- EDIT 2 --

As pointed by T.C. (thanks!) I've only (with streamV()) implemented a less efficient, less interesting and less useful version of std::visit().

Using std::visit() my example could become a lot simpler

#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;
}

I repeat: just for fun, because I don't think it's a good idea define operator<<() over a standard type.

I suggest the solution from T.C. that envelope the variant instance to stream in a specific class.




回答3:


Note: The following example was extracted from a comment from Igor Tandetnik on the question itself.

std::visit is a function in the standard library which can be used for this exact purpose:

#include <variant>
#include <iostream>

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

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

The snippet above is essentially a fancy way of writing:

#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';
    }
}



回答4:


I think you must use the get function from the std, to get the streamable types and not the variant type itself.

Something like that

std::cout << std::get<int>(a) << std::get<std::string>(b) << std::endl;


来源:https://stackoverflow.com/questions/47168477/how-to-stream-stdvariant

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!