13

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
max66
  • 65,235
  • 10
  • 71
  • 111
Tom
  • 3,281
  • 26
  • 33
  • 6
    Something along these lines: `std::visit([](const auto& v) { std::cout << v; }, a);` – Igor Tandetnik Nov 07 '17 at 22:53
  • 1
    Boost.Variant supports an insertion operator, it's not clear to me why this was omitted from `std::variant`. http://www.boost.org/doc/libs/1_65_1/doc/html/boost/operator_idp789915280.html – GManNickG Nov 07 '17 at 22:59

3 Answers3

17

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';
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 2
    Is there any reason for why this is not in the standard? – Claas Bontus Nov 08 '17 at 11:50
  • 1
    @ClaasBontus the usual answer: it wasn't proposed – sehe Nov 09 '17 at 09:26
  • 2
    Could someone clarify the line: template streamer(T) -> streamer; – LeDYoM Nov 09 '17 at 23:00
  • 3
    @LeDYoM It allows template argument deduction for streamer class, so you don't need to write `streamer{a}`. Check section **User-defined deduction guides** from [this article](http://en.cppreference.com/w/cpp/language/class_template_argument_deduction) – nnovich-OK Jan 13 '18 at 18:06
  • Note that the deduction guide is not necessary in C++20, which implicitly generates them for aggregate class templates. – Davis Herring Dec 25 '19 at 14:38
6

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.

max66
  • 65,235
  • 10
  • 71
  • 111
5

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';
    }
}
asynts
  • 2,213
  • 2
  • 21
  • 35