Невозможно передать std :: endl с перегруженным оператором << () для std :: option - PullRequest
0 голосов
/ 17 октября 2018

В этом ответе описывается, как транслировать автономный std::variant.Тем не менее, он не работает, когда std::variant хранится в std::unordered_map.

Следующий пример :

#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;
}

не удается скомпилировать с:

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,
                     ~~~~~~~~~~~~~~~~~~^~~

Почему это происходит?Как это можно исправить?

Ответы [ 3 ]

0 голосов
/ 17 октября 2018

По какой-то причине ваш код (который мне кажется правильным) пытается создать std::variant<> (пустые альтернативы) как в clang, так и в gcc.

Обходной путь, который я нашел, состоит в создании шаблона для специально непустого варианта.Поскольку std::variant не может быть пустым в любом случае, я думаю, что обычно полезно написать универсальные функции для непустых вариантов.

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;
}

С этим изменением ваш код работает для меня.


Я также выяснил, что если бы std::variant имел специализацию std::variant<> без конструктора с одним аргументом, эта проблема не возникла бы в первую очередь.См. Первые строки в https://godbolt.org/z/VGih_4 и как это работает.

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

Я делаю это только для того, чтобы проиллюстрировать это, я не обязательно рекомендую делать это.

0 голосов
/ 17 октября 2018

В [temp.arg.explicit] / 3 у нас есть это удивительное предложение:

Пакет параметров конечного шаблона, не выведенный иным образом, будет выведен в пустую последовательностьаргументов шаблона.

Что это значит?Что такое пакет параметров конечного шаблона?Что не означает иначе?Это все хорошие вопросы, на которые действительно нет ответов.Но это имеет очень интересные последствия.Подумайте:

template <typename... Ts> void f(std::tuple<Ts...>);
f({}); // ok??

Это ... правильно сформировано.Мы не можем вывести Ts..., поэтому мы выводим его как пустое.Это оставляет нас с std::tuple<>, который является совершенно допустимым типом - и совершенно допустимым типом, который может быть создан даже с {}.Итак, это компилируется!

Так что же происходит, когда то, что мы выводим из пустой пачки параметров, которую мы вызвали , не является допустимым типом?Вот пример :

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;
}

Потенциальный кандидат operator<<, но вычет не удался ... или так может показаться.Пока мы не заклинаем Ts... как пустое.Но Y<> - недопустимый тип!Мы даже не пытаемся выяснить, что мы не можем построить Y<> из std::endl - у нас уже произошел сбой .

Это в принципе та же ситуация, что и сvariant, поскольку variant<> не является допустимым типом.

Простое решение - просто изменить шаблон функции с принятия variant<Ts...> на variant<T, Ts...>.Это больше не может вывести на variant<>, что даже невозможно, поэтому у нас нет проблем.

0 голосов
/ 17 октября 2018

Проблема в std::endl, но я озадачен, почему ваша перегрузка лучше, чем та, из std :: basic_ostream :: operator << </a>, см. пример с Godbolt в реальном времени :

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

и удаление std::endl действительно решает проблему, смотрите в реальном времени на Wandbox .

Как указывает alfC, изменение вашего оператора для запретапустой вариант действительно решает проблему, посмотреть его вживую :

template<typename T, typename... Ts>
std::ostream& operator<<(std::ostream& os, const std::variant<T, Ts...>& v)
...