I have the following code, where I'm defining an ostream <<-operator overload for a variadic template argument type VariantContainer which contains a std::variant and uses the variadic template arguments from VariantContainer for std::variant:
#include <iostream>
#include <variant>
template<typename... VARIANT_TYPES>
struct VariantContainer {
std::variant<VARIANT_TYPES...> var;
};
template<typename... VARIANT_TYPES>
std::ostream& operator<<(std::ostream& os, const VariantContainer<VARIANT_TYPES...>& inst) {
std::visit([&os](auto&& elem){os<<elem;}, inst.var);
return os;
}
int main() {
//If the following line is commented out, a compiler error is raised
//std::cout << std::endl;
}
This example compiles on gcc-8, gcc-9 and gcc-10 with --std=c++17 (https://godbolt.org/z/5qvT3f).
But when I enable the std::cout-line, I get the following compiler error:
In file included from <source>:2:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant: In instantiation of 'constexpr const bool std::__detail::__variant::_Traits<>::_S_default_ctor':
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant:1279:11: required from 'class std::variant<>'
<source>:6:36: required from 'struct VariantContainer<>'
<source>:17:23: required from here
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant:286:4: error: invalid use of incomplete type 'struct std::__detail::__variant::_Nth_type<0>'
286 | is_default_constructible_v<typename _Nth_type<0, _Types...>::type>;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant:61:12: note: declaration of 'struct std::__detail::__variant::_Nth_type<0>'
61 | struct _Nth_type;
| ^~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant: In instantiation of 'class std::variant<>':
<source>:6:36: required from 'struct VariantContainer<>'
<source>:17:23: required from here
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant:1298:39: error: static assertion failed: variant must have at least one alternative
1298 | static_assert(sizeof...(_Types) > 0,
| ~~~~~~~~~~~~~~~~~~^~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/move.h:57,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/nested_exception.h:40,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/exception:148,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/ios:39,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/ostream:38,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/iostream:39,
from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/type_traits: In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = void]':
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant:1361:2: required from 'class std::variant<>'
<source>:6:36: required from 'struct VariantContainer<>'
<source>:17:23: required from here
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/type_traits:2554:11: error: no type named 'type' in 'struct std::enable_if<false, void>'
2554 | using enable_if_t = typename enable_if<_Cond, _Tp>::type;
| ^~~~~~~~~~~
ASM generation compiler returned: 1
In file included from <source>:2:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant: In instantiation of 'constexpr const bool std::__detail::__variant::_Traits<>::_S_default_ctor':
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant:1279:11: required from 'class std::variant<>'
<source>:6:36: required from 'struct VariantContainer<>'
<source>:17:23: required from here
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant:286:4: error: invalid use of incomplete type 'struct std::__detail::__variant::_Nth_type<0>'
286 | is_default_constructible_v<typename _Nth_type<0, _Types...>::type>;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant:61:12: note: declaration of 'struct std::__detail::__variant::_Nth_type<0>'
61 | struct _Nth_type;
| ^~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant: In instantiation of 'class std::variant<>':
<source>:6:36: required from 'struct VariantContainer<>'
<source>:17:23: required from here
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant:1298:39: error: static assertion failed: variant must have at least one alternative
1298 | static_assert(sizeof...(_Types) > 0,
| ~~~~~~~~~~~~~~~~~~^~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/move.h:57,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/nested_exception.h:40,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/exception:148,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/ios:39,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/ostream:38,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/iostream:39,
from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/type_traits: In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = void]':
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/variant:1361:2: required from 'class std::variant<>'
<source>:6:36: required from 'struct VariantContainer<>'
<source>:17:23: required from here
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/type_traits:2554:11: error: no type named 'type' in 'struct std::enable_if<false, void>'
2554 | using enable_if_t = typename enable_if<_Cond, _Tp>::type;
| ^~~~~~~~~~~
Execution build compiler returned: 1
In my opinion it looks like when the compiler sees std::endl, it is trying to instantiate VariantContainer with zero template arguments (which is not allowed, because std::variant static_asserts this).
But why does the compiler do that and how can I prevent this?
Of course, it's clear to me, that I can create a to_string method in VariantContainer to prevent this or provide a more general version of the std::ostream <<-operator (like template<typename T> std::ostream& operator<<(std::ostream &, const T& ) {...} and constrain it via std::enable_if. But I don't really like this.