byteunit
byteunit

Reputation: 1063

Using std::endl in ostream '<<'-operator overload with variadic template class containing a variant member leads to compiler error

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.

Upvotes: 2

Views: 206

Answers (2)

Jarod42
Jarod42

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<>)

  • Changing operator<< to
template <typename T, typename... VARIANT_TYPES>
std::ostream& operator<<(std::ostream& os, const VariantContainer<T, VARIANT_TYPES...>& inst);

Upvotes: 3

byteunit
byteunit

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

Related Questions