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>
// 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;
}
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?
The problem is the
std::endl
but I am puzzled why your overload is a better match than the one from std::basic_ostream::operator<<, see godbolt live example:and removing the
std::endl
indeed fixes the problem, see it live on Wandbox.As alfC points out altering your operator to disallow an empty variant does indeed fix the issue, see it live:
In [temp.arg.explicit]/3, we have this amazing sentence:
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:
This is... well-formed. We can't deduce
Ts...
so we deduce it as empty. That leaves us withstd::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:
The
operator<<
is a potential candidate, but deduction fails... or so it would seem. Until we conjure upTs...
as empty. ButY<>
is an invalid type! We don't even try to find out that we can't construct aY<>
fromstd::endl
- we have already failed.This is fundamentally the same situation you have with
variant
, becausevariant<>
is not a valid type.The easy fix is to simply change your function template from taking a
variant<Ts...>
to avariant<T, Ts...>
. This can no longer deduce tovariant<>
, which isn't even a possible thing, so we don't have a problem.For some reason, your code (which looks correct to me) is trying to instantiate
std::variant<>
(empty alternatives) both in clang and gcc.The workaround I found is to make a template for a specifically non-empty variant. Since
std::variant
cannot be empty anyway, I think it is usually good to write generic functions for non-empty variants.With this change, your code works for me.
I also figured out that if
std::variant
had a specialization ofstd::variant<>
without a single-argument constructor, this problem would not have happened in the first place. See the first lines in https://godbolt.org/z/VGih_4 and how it makes it work.I am doing this just to illustrate the point, I don't necessarely recommend doing this.