Ao usar apenas ponteiros/referências para std::variant<T...>
definir de T...
onde parte de T é declarada apenas para frente - tenho esse problema que não consigo nem usar ponteiros nem referências para isso std::variant
- porque suas classes bases queriam ser instanciadas - e exigem std::is_default_constructible
outras características.
Veja código de exemplo:
#include <variant>
struct A;
struct B;
using AB = std::variant<A,B>;
AB* ab();
template <typename T>
void usePointer(T*){}
int main() {
// fails to compile because std::variant gets instantiated here!
usePointer(ab());
}
Para simplificar o exemplo - isto é o que acontece como vejo:
struct A;
struct B;
template <typename T>
void usePointer(T*){}
template <bool> class SomeBase {};
template <typename T>
struct Some : SomeBase<std::is_default_constructible_v<T>>
{};
Some<A>* someA();
int main() {
// this will not compile - because this
// SomeBase<std::is_default_constructible_v<T>>
// does not compile - because A is unknown
usePointer(someA());
}
/opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/type_traits: In instantiation of 'constexpr const bool std::is_default_constructible_v<A>':
<source>:12:29: required from 'struct Some<A>'
12 | struct Some : SomeBase<std::is_default_constructible_v<T>>
| ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:21:23: required from here
21 | usePointer(someA());
| ^
/opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/type_traits:3385:54: error: invalid use of incomplete type 'struct A'
3385 | inline constexpr bool is_default_constructible_v = __is_constructible(_Tp);
| ^~~~~~~~~~~~~~~~~~~~~~~
<source>:3:8: note: forward declaration of 'struct A'
3 | struct A;
| ^
Compiler returned: 1
Todos os 3 compiladores (gcc, clang, msvc) se comportam/rejeitam da mesma maneira - veja: https://godbolt.org/z/5cjf1Pv4d
A pergunta: isso está correto? Para mim, isso é realmente um bug - seja nas implementações dos compiladores ou no padrão - porém não consigo encontrar nada no padrão que exija esse comportamento.
O "engraçado" é que quando o modelo de classe também é declarado adiante - tudo funciona. Quero dizer - este código é aceito por todos os compiladores:
struct A;
template <typename T>
void usePointer(T*){}
template <typename T> struct Some;
Some<A>* someA();
int main() {
usePointer(someA());
}
Isso ocorre porque
std::variant
precisa ser instanciado para ADL: pode haver umfriend void usePointer(std::variant<A, B>*)
declarado dentrostd::variant
que ele teria que usar, então o modelo precisa ser instanciado (causando erro porquestd::variant
precisa que seus tipos sejam completos quando for instanciado).A solução é não usar ADL:
https://eel.is/c++draft/basic.lookup.argdep#1
usePointer
não encontra um membro de classe, função de escopo de bloco ou modelo de não função/função.https://eel.is/c++draft/basic.lookup.argdep#3.4
Então
T = std::variant<A, B>*
,U = std::variant<A, B>
https://eel.is/c++draft/basic.lookup.argdep#3.2.sentence-1
(Antes de CWG2857 , isso tecnicamente não precisava instanciar a classe. Mas esse não é o comportamento esperado porque nenhuma
friend
função nos modelos poderia ser encontrada antes de serem instanciadas implicitamente em outro lugar, e isso era um defeito padrão)Então a classe precisa ser do tipo completa para ver suas bases.
https://eel.is/c++draft/temp.inst#2
Então temos que instanciá-lo.
É também por isso que seu exemplo
struct A;
é compilado: não é um modelo, portanto não precisa ser instanciado.Se você fez isso, a integridade afetará o programa:
... seu programa se tornaria NDR mal formado ( https://eel.is/c++draft/temp.dep.res#temp.point-7.sentence-4 )