c++ - sencillos - No se puede transmitir std:: endl con el operador sobrecargado<<() para std:: variant
sobrecarga de operadores unarios en c++ (3)
El problema es std::endl
pero me sorprende que su sobrecarga sea mejor que la de std::basic_ostream::operator<< , vea el ejemplo de godbolt live :
<source>:29:12: note: in instantiation of template class ''std::variant<>'' requested here
<< std::endl;
^
y eliminar std::endl
soluciona el problema, véalo en vivo en Wandbox .
Como alfC señala que la modificación de su operador para no permitir una variante vacía realmente soluciona el problema, véalo en vivo :
template<typename T, typename... Ts>
std::ostream& operator<<(std::ostream& os, const std::variant<T, Ts...>& v)
Esta respuesta describe cómo transmitir una std::variant
estándar std::variant
. Sin embargo, no parece funcionar cuando std::variant
se almacena en un std::unordered_map
.
El siguiente example :
#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;
}
falla al compilar con:
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,
~~~~~~~~~~~~~~~~~~^~~
¿Por que sucede? ¿Cómo es posible arreglarlo?
En [temp.arg.explicit]/3 , tenemos esta frase sorprendente:
Un paquete de parámetros de la plantilla final que no se deduzca de otra manera se deducirá a una secuencia vacía de argumentos de la plantilla.
¿Qué significa esto? ¿Qué es un paquete de parámetros de plantillas finales? ¿Qué no se deduce lo contrario? Estas son todas buenas preguntas que realmente no tienen respuestas. Pero esto tiene consecuencias muy interesantes. Considerar:
template <typename... Ts> void f(std::tuple<Ts...>);
f({}); // ok??
Esto está ... bien formado. No podemos deducir Ts...
así que lo deducimos como vacío. Eso nos deja con std::tuple<>
, que es un tipo perfectamente válido, y un tipo perfectamente válido que incluso puede ser instanciado con {}
. Así que esto se compila!
Entonces, ¿qué sucede cuando lo que deducimos del paquete de parámetros vacío que evocamos no es un tipo válido? Aquí hay un ejemplo :
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;
}
El operator<<
es un candidato potencial, pero la deducción falla ... o eso parece. Hasta que evocemos Ts...
como vacíos. Pero Y<>
es un tipo inválido! Ni siquiera intentamos descubrir que no podemos construir una Y<>
partir de std::endl
; ya hemos fallado .
Esta es fundamentalmente la misma situación que tiene con la variant
, porque la variant<>
no es un tipo válido.
La solución fácil es simplemente cambiar la plantilla de función de una variant<Ts...>
a una variant<T, Ts...>
. Esto ya no se puede deducir a la variant<>
, que ni siquiera es una cosa posible, por lo que no tenemos un problema.
Por alguna razón, su código (que me parece correcto) está intentando instanciar std::variant<>
(alternativas vacías) tanto en clang como en gcc.
La solución que encontré es hacer una plantilla para una variante específicamente no vacía. Como std::variant
no puede estar vacío, creo que normalmente es bueno escribir funciones genéricas para variantes no vacías.
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;
}
Con este cambio, tu código funciona para mí.
También me di cuenta de que si std::variant
tuviera una especialización de std::variant<>
sin un constructor de argumento único, este problema no habría ocurrido en primer lugar. Vea las primeras líneas en https://godbolt.org/z/VGih_4 y cómo lo hace funcionar.
namespace std{
template<> struct variant<>{ ... no single-argument constructor, optionally add static assert code ... };
}
Estoy haciendo esto solo para ilustrar el punto, no necesariamente recomiendo hacerlo.