Descobri por acidente, ao modificar o código existente, que uma declaração de alias foi criada std::optional<void>
. Veio de um código de modelo como este:
using ret_t = std::invoke_result_t<Fn, Args...>;
using opt_ret_t = std::optional<ret_t>;
Posteriormente, o código discrimina o caso quando o tipo de retorno é (not) void
, portanto, nunca há realmente um optional
objeto criado.
Para ter certeza de que não estava faltando nada, adicionei uma afirmação nesse ramo do código e tudo compila:
static_assert(std::is_same_v<opt_ret_t, std::optional<void>>);
Não há referências opcionais, funções, arrays ou cv void; um programa está mal formado se instanciar um opcional com esse tipo.
Portanto, independentemente dos compiladores não reclamarem, esse código está mal formado?
Existem circunstâncias específicas em que modelos (de classe) são instanciados implicitamente: consulte [temp.inst], embora em alguns casos isso possa variar de acordo com a implementação. Em nenhum caso a mera menção de um template-id causa instanciação; normalmente ocorre quando o tipo precisa ser completo .
Em C++20, é possível declarar um modelo com restrições que entram em vigor mesmo quando a especialização do modelo é meramente nomeada:
[res.on.functions]/2.5 afirma que o comportamento é indefinido se você usar um tipo incompleto como argumento de modelo "ao instanciar um componente de modelo ou avaliar um conceito, a menos que seja especificamente permitido para esse componente" na biblioteca padrão.
void
é um tipo incompleto ([basic.types.general]/5) estd::optional
não é um dos modelos de biblioteca padrão que permite tipos incompletos ([optional.optional.general]/3). Portanto, se você instanciarstd::optional<void>
, o programa poderá falhar na compilação ou poderá compilar e se comportar de maneira imprevisível em tempo de execução (mas, na prática, a primeira coisa é o que acontece).No entanto, fazer algo assim
não instancia
std::optional<void>
. De acordo com [temp.inst]/2 e [temp.inst]/11, uma especialização de modelo de classe é instanciada implicitamente apenas quando é usada de uma forma que exige que a especialização seja completa, ou quando a completude da especialização afeta o semântica do programa. Quando você simplesmente menciona o nome de uma especialização de modelo de classe ou declara um alias para ela, a especialização de modelo de classe não precisa ser completa, nem há diferença entre declarar um alias para um tipo completo ou incompleto. Portanto, neste caso não obtemos uma instanciação implícita.A biblioteca padrão não possui uma regra correspondente para usos não instanciados de especializações de modelo de classe. Na verdade, se for um tipo de classe
U
incompleta , será útil poder fazer algo assim:Usar um tipo como tipo de retorno não exige que ele seja concluído até que a função seja realmente definida, portanto, é legal usá-lo
std::optional<U>
como tipo de retorno em uma declaração sem definição antes deU
ser concluída.Se houvesse alguma regra que proibisse especificamente usos não instanciados de
std::optional<void>
um particular, ela precisaria ser encontrada nastd::optional
própria descrição. Uma vez questd::optional
é definido comonada de ruim acontecerá quando você simplesmente passar
void
para o parâmetro templateT
sem instanciar o modelo de classe, mas como Davis Herring aponta, pode haver alguns modelos de classe onde apenas mencionar o nome de uma especialização seria inválido mesmo sem instanciação.std::optional
simplesmente não é um deles.