Reputation: 1063
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_assert
s 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.
Upvotes: 2
Views: 206
Reputation: 217275
std::endl
is a function template, so has overloads.
With
std::cout << std::endl;
we have to take (correct) address of an overloaded function.
For this, we have to see between each operator<<
is there is some which constraint the signature.
There is indeed the ones from basic_stream
, in particular
basic_ostream& operator<<(
std::basic_ios<CharT,Traits>& (*func)(std::basic_ios<CharT,Traits>&) );
(which is the expected best match).
but we also have to check other, in particular, the problematic one:
template<typename... VARIANT_TYPES>
std::ostream& operator<<(std::ostream& os, const VariantContainer<VARIANT_TYPES...>& inst);
and as VARIANT_TYPES
would not be deducible from function set, VARIANT_TYPES
is empty pack.
Now checking if VariantContainer<>(std::endl)
would force signature of the function, so instantiate VariantContainer<>
constructors which does hard error due to std::variant<>
.
Now possible solutions should avoid hard error with
template<typename... VARIANT_TYPES>
std::ostream& operator<<(std::ostream& os, const VariantContainer<VARIANT_TYPES...>& inst);
providing (valid or even incomplete) specialization for class VariantContainer<>
Changing primary template to
template <typename T, typename... Ts>
class VariantContainer;
(no need to change operator <<
, as SFINAE will apply to invalid VariantContainer<>
)
operator<<
totemplate <typename T, typename... VARIANT_TYPES>
std::ostream& operator<<(std::ostream& os, const VariantContainer<T, VARIANT_TYPES...>& inst);
Upvotes: 3
Reputation: 1063
Meanwhile, I have tried to define VariantContainer like this:
template<typename VARIANT_TYPE, typename... VARIANT_TYPES>
struct VariantContainer {
std::variant<VARIANT_TYPE, VARIANT_TYPES...> var;
};
just to make sure that there must be given one template argument.
And now it works!
#include <iostream>
#include <variant>
struct A{
int a;
};
struct B{
int b;
};
std::ostream& operator<<(std::ostream& os, const A& inst) {os << inst.a; return os;}
std::ostream& operator<<(std::ostream& os, const B& inst) {os << inst.b; return os;}
template<typename VARIANT_TYPE, typename... VARIANT_TYPES>
struct VariantContainer {
std::variant<VARIANT_TYPE, 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() {
VariantContainer<A, B> insta{A{1}};
VariantContainer<A, B> instb{B{1}};
std::cout << "If I want to print something, I get a compiler error." << insta << " " << instb<< std::endl;
}
Upvotes: 0