Can't stream std::endl with overloaded operator<<() for std::variant

  • A+

This answer describes how to stream a standalone std::variant. However, it doesn't seem to work when std::variant is stored in a std::unordered_map.

The following example:

#include <iostream> #include <string> #include <variant> #include <complex> #include <unordered_map>  // 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; } 

fails to compile with:

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

Why does it happen? How is it possible to fix it?


In [temp.arg.explicit]/3, we have this amazing sentence:

A trailing template parameter pack not otherwise deduced will be deduced to an empty sequence of template arguments.

What does this mean? What is a trailing template parameter pack? What does not otherwise deduced mean? These are all good questions that don't really have answers. But this has very interesting consequences. Consider:

template <typename... Ts> void f(std::tuple<Ts...>); f({}); // ok?? 

This is... well-formed. We can't deduce Ts... so we deduce it as empty. That leaves us with std::tuple<>, which is a perfectly valid type - and a perfectly valid type that can even be instantiated with {}. So this compiles!

So what happens when the thing we deduce from the empty parameter pack we conjured up isn't a valid type? Here's an example:

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

The operator<< is a potential candidate, but deduction fails... or so it would seem. Until we conjure up Ts... as empty. But Y<> is an invalid type! We don't even try to find out that we can't construct a Y<> from std::endl - we have already failed.

This is fundamentally the same situation you have with variant, because variant<> is not a valid type.

The easy fix is to simply change your function template from taking a variant<Ts...> to a variant<T, Ts...>. This can no longer deduce to variant<>, which isn't even a possible thing, so we don't have a problem.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: