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

前端 未结 3 508
不思量自难忘°
不思量自难忘° 2020-12-14 07:47

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::unorde

相关标签:
3条回答
  • 2020-12-14 08:45

    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:

    <source>:29:12: note: in instantiation of template class 'std::variant<>' requested here
            << std::endl;
               ^
    

    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:

    template<typename T, typename... Ts>
    std::ostream& operator<<(std::ostream& os, const std::variant<T, Ts...>& v)
    
    0 讨论(0)
  • 2020-12-14 08:46

    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.

    template<typename T, typename... Ts>
    std::ostream& operator<<(std::ostream& os, const std::variant<T, Ts...>& v)
    {
        std::visit([&os](auto&& arg) {
            os << arg;
        }, v);
        return os;
    }
    

    With this change, your code works for me.


    I also figured out that if std::variant had a specialization of std::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.

    namespace std{
       template<> struct variant<>{ ... no single-argument constructor, optionally add static assert code ... };
    }
    

    I am doing this just to illustrate the point, I don't necessarely recommend doing this.

    0 讨论(0)
  • 2020-12-14 08:48

    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.

    0 讨论(0)
提交回复
热议问题