Não é possível transmitir std :: endl com o operador sobrecarregado << () para std :: variante

Esta resposta descreve como transmitir um std::variant autônomo. No entanto, parece não funcionar quando std::variant é armazenado em um std::unordered_map .

O exemplo a seguir:

 #include  #include  #include  #include  #include  // https://stackoverflow.com/a/46893057/8414561 template std::ostream& operator<<(std::ostream& os, const std::variant& 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>> map{ {0, 4}, {1, "hello"}, {2, 3.14}, {3, 2. + 3i} }; for (const auto& [key, value] : map) std::cout << key << "=" << value << std::endl; } 

não consegue compilar com:

 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' is_default_constructible_v<typename _Nth_type::type>; ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /usr/local/include/c++/8.1.0/variant:58:12: note: declaration of 'struct std::__detail::__variant::_Nth_type' 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 isso acontece? Como é possível consertar isso?

Em [temp.arg.explicit] / 3 , temos esta frase incrível:

Um pacote de parâmetros de modelo final não deduzido de outra maneira será deduzido a uma sequência vazia de argumentos de modelo.

O que isto significa? O que é um pacote de parâmetros de modelo à direita? O que não deduz de outra maneira significa? Estas são todas boas perguntas que realmente não têm respostas. Mas isso tem consequências muito interessantes. Considerar:

 template  void f(std::tuple); f({}); // ok?? 

Isso é … bem formado. Não podemos deduzir Ts... então deduzimos isso como vazio. Isso nos deixa com std::tuple<> , que é um tipo perfeitamente válido – e um tipo perfeitamente válido que pode até mesmo ser instanciado com {} . Então isso compila!

Então, o que acontece quando a coisa que deduzimos do pacote de parâmetros vazio que conjuramos não é um tipo válido? Aqui está um exemplo :

 template  struct Y { static_assert(sizeof...(Ts)>0, "!"); }; template  std::ostream& operator<<(std::ostream& os, Y const& ) { return os << std::endl; } 

O operator<< é um candidato em potencial, mas a dedução falha ... ou assim parece. Até nós conjurarmos Ts... como vazios. Mas Y<> é um tipo inválido! Nós nem sequer tentamos descobrir que não podemos construir um Y<> de std::endl - nós já falhamos .

Isso é fundamentalmente a mesma situação que você tem com a variant , porque a variant<> não é um tipo válido.

A solução mais fácil é simplesmente alterar o modelo de function de uma variant para uma variant . Isso não pode mais ser deduzido à variant<> , o que não é nem uma coisa possível, então não temos um problema.

Por alguma razão, seu código (que parece correto para mim) está tentando instanciar std::variant<> (alternativas vazias) tanto no clang quanto no gcc.

A solução alternativa que encontrei é criar um modelo para uma variante especificamente não vazia. Como o std::variant não pode estar vazio de qualquer forma, acho que geralmente é bom escrever funções genéricas para variantes não vazias.

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

Com essa mudança, seu código funciona para mim.


Eu também descobri que se std::variant tivesse uma especialização de std::variant<> sem um construtor de argumento único, este problema não teria acontecido em primeiro lugar. Veja as primeiras linhas em https://godbolt.org/z/VGih_4 e como isso funciona.

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

Eu estou fazendo isso apenas para ilustrar o ponto, eu não recomendo necessariamente fazer isso.

O problema é o std::endl mas estou intrigado porque sua sobrecarga é melhor do que a do std :: basic_ostream :: operator << , veja godbolt live example :

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

e remover o std::endl realmente corrige o problema, veja-o ao vivo no Wandbox .

Como o AlfC aponta para alterar sua operadora para não permitir uma variante vazia, de fato, corrija o problema, veja-o ao vivo :

 template std::ostream& operator<<(std::ostream& os, const std::variant& v)