Colisión de espacio de nombres C ++ en el constructor de copias

33

Tengo el siguiente 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;
};

Los compiladores de C ++ se quejan en "c = foo.b" porque A :: Foo no tiene un miembro llamado b. Si cambio el tipo de parámetro Bar con :: Foo, funciona.

Mi pregunta es cuál es la razón detrás de este comportamiento (supongo que tiene que ver con el hecho de que la herencia hace que Bar ingrese el espacio de nombres A, pero no puedo encontrar ninguna documentación que respalde esta teoría.

Vincent Le Ligeour
fuente
8
Yo creo que depende de las operaciones de búsqueda argumento relacionado. He etiquetado a "abogado de idiomas" porque creo que buscas respuestas que hacen referencia al estándar de idioma. Y una muy buena primera pregunta! Hace que todo valga la pena.
Betsabé el
No ingresa el espacio de nombres A, que puede ver si deja Barheredar de otra estructura A. Entonces no hay ambigüedad. Se parece más a la herencia se suma todo, desde A::Fooque Barincluyendo la resolución de Fooa A::Foo. Lo siento, realmente no puedo expresarlo con más precisión.
n314159
@Bathsheba ¿Se refiere a la búsqueda de nombres dependientes del tipo de argumento para encontrar nombres de funciones (o nombres de plantillas de funciones) o nombres dependientes en las plantillas?
curioso

Respuestas:

22

Cada clase tiene su nombre inyectado como miembro. Entonces puedes nombrar A::Foo::Foo. Esto se llama el nombre de la clase inyectada.

[clase]

2 Se inserta un nombre de clase en el ámbito en el que se declara inmediatamente después de ver el nombre de clase. El nombre de la clase también se inserta en el ámbito de la clase misma; Esto se conoce como el nombre de la clase inyectada. Para fines de verificación de acceso, el nombre de clase inyectado se trata como si fuera un nombre de miembro público.

[búsqueda básica]

3 El nombre de la clase inyectada de una clase también se considera miembro de esa clase a efectos de ocultación y búsqueda de nombres.

Debido a que la búsqueda de nombre no calificado del tipo de argumento comienza en el alcance de la clase Bar, continuará en el alcance de su clase base para dar cuenta de cualquier miembro allí. Y lo encontrará A::Foo::Foocomo un nombre de tipo.

Si desea utilizar el nombre de tipo global, simplemente califíquelo por su espacio de nombres (global) circundante.

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

Lo que está haciendo una búsqueda totalmente calificada en un ámbito donde el nombre de la clase inyectada no aparece.

Para una pregunta complementaria de "por qué", vea

StoryTeller - Unslander Monica
fuente
55
@TedLyngmo: ADL ocurre con llamadas a funciones, nada relevante en esos pasajes específicos.
StoryTeller - Unslander Monica
Oki, estaba leyendo y no estaba seguro. ¡Gracias!
Ted Lyngmo
3
Esto lleva a lo muy divertido, struct Bar:: A::Foo::Foo::Foo::Foo::Foo {}; pero hay contextos donde se A::Foo::Foodesigna al constructor y, por lo tanto, no puede continuar agregando tantos Foocomo desee. Esto es similar (pero con un mecanismo completamente diferente) al hecho de que se puede llamar a una función fde esta manera: (************f)().
Programador
@AProgrammer - De hecho. Y uno puede construir ejemplos aún más divertidos .
StoryTeller - Unslander Monica
Esta respuesta ciertamente explica el "qué". ¿Se puede mejorar para agregar el "por qué"? Como en, ¿cuál es el propósito de esta regla? ¿Qué casos de uso mejora o hace posible?
davidbak
2

No es una respuesta completa, solo código que muestra (ya que compila) que Barno ingresa el namespace A. Puede ver que al heredar de A::Foo1allí no hay ningún problema con la ambigüedad, Fooque sería diferente si esta herencia dejara 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
fuente