Colisão de namespace C ++ no construtor de cópias

33

Eu tenho o seguinte código:

namespace A {
    struct Foo {
        int a;
    };
}

struct Foo {
    int b;
};

struct Bar : public A::Foo {
    Bar(Foo foo) {
        c = foo.b;
    }
    int c;
};

Compiladores C ++ reclamam em "c = foo.b" porque A :: Foo não possui um membro chamado b. Se eu alterar o tipo de parâmetro Bar com :: Foo, ele funcionará.

Minha pergunta é qual é o racional por trás desse comportamento (suponho que tenha a ver com o fato de a herança fazer Bar entrar no espaço de nomes A, mas não consigo encontrar nenhuma documentação para apoiar essa teoria.

Vincent Le Ligeour
fonte
8
Eu acho que é uma pesquisa dependente de argumento relacionada. Eu marquei "language-advogado" porque acho que você procura respostas que façam referência ao padrão da linguagem. E uma primeira pergunta muito boa! Faz tudo valer a pena.
Bathsheba
Ele não entra no espaço para nome A, que você pode ver se deixar Barherdar de outra estrutura A. Então não há ambiguidade. É mais como se a herança adicionasse tudo, desde A::Fooaté Bara resolução de Fooaté A::Foo. Desculpe, não posso realmente expressar isso de forma mais precisa.
N314159 28/11/19
@Bathsheba Você quer dizer pesquisa de nome dependente do tipo de argumento para encontrar nomes de funções (ou nomes de modelos de funções) ou nomes dependentes em modelos?
curiousguy

Respostas:

22

Toda classe tem seu nome inserido como membro. Então você pode nomear A::Foo::Foo. Isso é chamado de nome da classe injetada.

[classe]

2 Um nome de classe é inserido no escopo em que é declarado imediatamente após a visualização do nome da classe. O nome da classe também é inserido no escopo da própria classe; isso é conhecido como o nome da classe injetado. Para fins de verificação de acesso, o nome da classe injetado é tratado como se fosse um nome de membro público.

[pesquisa básica]

3 O nome da classe injetada de uma classe também é considerado membro dessa classe para fins de ocultação e pesquisa de nome.

Como a pesquisa de nome não qualificado do tipo de argumento começa no escopo da classe Bar, ela continuará no escopo de sua classe base para contabilizar qualquer membro lá. E será encontrado A::Foo::Foocomo um nome de tipo.

Se você deseja usar o nome do tipo global, qualifique-o simplesmente pelo espaço para nome (global) ao redor.

Bar(::Foo foo) {
    c = foo.b;
}

Que está fazendo uma pesquisa totalmente qualificada em um escopo em que o nome da classe injetada não aparece.

Para uma pergunta "por que", consulte

Contador de Histórias - Monica Sem Calúnia
fonte
5
@TedLyngmo - o ADL acontece com chamadas de função, nada relevante nessas passagens específicas.
StoryTeller - Unslander Monica 28/11/19
Oki, eu estava lendo e não tinha certeza. Obrigado!
Ted Lyngmo
3
Isso leva ao muito divertido, struct Bar:: A::Foo::Foo::Foo::Foo::Foo {}; mas há contextos em que A::Foo::Foodesigna o construtor e, portanto, não é possível continuar adicionando quantos Foovocê desejar. Isto é semelhante (mas com um mecanismo completamente diferente) para o fato de que você pode chamar uma função fdesta forma: (************f)().
AProgrammer 28/11/19
@AProgrammer - De fato. E pode-se construir exemplos ainda mais divertidos .
StoryTeller - Unslander Monica 28/11/19
Esta resposta certamente explica o "o quê". Pode ser melhorado adicionar o "porquê"? Como em qual é o propósito desta regra? Quais casos de uso isso melhora ou possibilita?
Davidbak 29/11/19
2

Não é uma resposta completa, apenas o código que mostra (uma vez que é compilado) que Barnão entra no namespace A. Você pode ver que quando herdando A::Foo1não há nenhum problema com a ambiguidade de Fooque seria diferente se essa herança permite Barentrar A.

namespace A {
    struct Foo {
        int a;
    };

    struct Foo1 {
        int a;
    };
}

struct Foo {
    int b;
};

struct Bar : public A::Foo1 {
    Bar(Foo foo) {
        c = foo.b;
    }
    int c;
};
n314159
fonte