T precisa ser um tipo completo para ser usado em `std :: declval <T>`?

11

Considere este exemplo (vindo daqui ):

#include <type_traits>
#include <iostream>
template <typename U>
struct A {
};

struct B {
   template <typename F = int>
   A<F> f() { return A<F>{}; }

   using default_return_type = decltype(std::declval<B>().f());
};

int main()
{
    B::default_return_type x{};
    std::cout << std::is_same< B::default_return_type, A<int>>::value;
}

Ele é compilado sem erros no gcc9.2, mas o gcc7.2 e o clang 10.0.0 reclamam por Bnão serem concluídos. O erro de Clangs é:

prog.cc:11:58: error: member access into incomplete type 'B'
   using default_return_type = decltype(std::declval<B>().f());
                                                         ^
prog.cc:7:8: note: definition of 'B' is not complete until the closing '}'
struct B {
       ^
prog.cc:16:8: error: no type named 'default_return_type' in 'B'
    B::default_return_type x{};
    ~~~^
prog.cc:17:35: error: no member named 'default_return_type' in 'B'
    std::cout << std::is_same< B::default_return_type, A<int>>::value;
                               ~~~^
idclev 463035818
fonte
11
O título da pergunta não parece corresponder ao erro? Para mim, parece que o GCC reclama .f(). Isso faz sentido; o tipo incompleto Bnão tem um membro f.
MSalters
@ MSalters eu pensei o mesmo, mas então qual é o verdadeiro problema aqui? Eu diria que uma vez que você tem um exemplo de std::declvalque não importa mais se o tipo foi completa ou não (e eu acho que estou errado com isso)
idclev 463035818
[expr.ref] / 2 (C ++ 11) diz sobre o acesso de membro da classe: "Para a primeira opção (ponto), a primeira expressão deve ter o tipo de classe completo" . E Bnão é completo nem considerado completo em alias-declaration.
Language Lawyer
@LanguageLawyer Não encontrei a frase que você citou, mas apenas "O tipo de classe deve ser completo, a menos que o acesso do membro da classe apareça na definição dessa classe"
idclev 463035818
11
@LanguageLawyer ok, então eu concordo que minha interpretação foi desativada e parece que algo mudou desde o c ++ 11, o que faz com que o descrito acima seja aprovado nos padrões mais recentes, mas não no c ++ 11. Você se importaria de escrever uma resposta?
Idclev 463035818

Respostas:

9

A origem do erro não é std::declval, mas o acesso incompleto dos membros da classe.

Até a resolução do CWG1836 ser incorporada há 2,5 anos, o padrão exigia que a classe fosse concluída em uma expressão de acesso de membro da classe ( E1.E2).
[expr.ref] / 2 em C ++ 11 :

Para a primeira opção (ponto), a primeira expressão deve ter um tipo de classe completo.

[expr.ref] / 2 em C ++ 17 :

Para a primeira opção (ponto), a primeira expressão deve ser um glvalue com o tipo de classe completo.

E uma classe não é considerada completa por alias-declarationsi só member-specification.
[class.mem] / 6 em C ++ 17 :

Uma classe é considerada um tipo de objeto completamente definido ([basic.types]) (ou tipo completo) no fechamento }do especificador de classe . Dentro da especificação de membro da classe , a classe é considerada completa nos corpos das funções, argumentos padrão, especificadores noexcept e inicializadores de membros padrão (incluindo itens nas classes aninhadas). Caso contrário, é considerado incompleto dentro de sua própria especificação de membro de classe .

Advogado de idiomas
fonte
8

De [declval] :

Comentários: O parâmetro Tdo modelo de declvalpode ser um tipo incompleto.

Este texto está presente desde C ++ 11 (portanto, não é possível que os compiladores estejam em conformidade com um padrão anterior)

AndyG
fonte
incrível, era o que eu estava esperando. Parece que o gcc o corrigiu, ainda não clang ()
idclev 463035818
@ formerlyknownas_463035818: Meu primeiro pensamento foi que Tdeveria ser absolutamente um tipo completo. Ainda bem que verifiquei o padrão.
AndyG