Kolizja przestrzeni nazw C ++ w konstruktorze kopii

33

Mam następujący kod:

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

struct Foo {
    int b;
};

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

Kompilatory C ++ narzekają na „c = foo.b”, ponieważ A :: Foo nie ma członka o nazwie b. Jeśli zmienię typ parametru Bar za pomocą :: Foo to działa.

Moje pytanie brzmi, jakie jest racjonalne uzasadnienie tego zachowania (przypuszczam, że ma to związek z faktem, że dziedzictwo powoduje, że Bar wchodzi w przestrzeń nazw A, ale nie mogę znaleźć żadnej dokumentacji na poparcie tej teorii.

Vincent Le Ligeour
źródło
8
Myślę, że jest to związane z wyszukiwaniem zależnym od argumentów. Oznaczyłem „prawnika języka”, ponieważ myślę, że szukasz odpowiedzi, które odnoszą się do standardu językowego. I bardzo dobre pierwsze pytanie! Sprawia, że ​​wszystko się opłaca.
Batszeba
Nie wchodzi w przestrzeń nazw A, którą możesz zobaczyć, jeśli pozwolisz Barodziedziczyć po innej strukturze A. Wtedy nie ma dwuznaczności. To jest bardziej jak dziedziczenie dodaje wszystko od A::Foodo Barwłącznie z rozdzielczością Foodo A::Foo. Przepraszam, nie potrafię tego dokładniej wyrazić.
n314159,
@Bathsheba Czy masz na myśli wyszukiwanie nazw zależne od typu argumentu w celu znalezienia nazw funkcji (lub nazw szablonów funkcji) lub nazw zależnych w szablonach?
ciekawy

Odpowiedzi:

22

Każda klasa ma swoją nazwę wstrzykniętą jako członek. Więc możesz nazwać A::Foo::Foo. Nazywa się to nazwą klasy wstrzykniętej.

[klasa]

2 Nazwa klasy jest wstawiana do zakresu, w którym jest zadeklarowana natychmiast po zobaczeniu nazwy klasy. Nazwa klasy jest również wstawiana w zakres samej klasy; jest to znane jako nazwa klasy zastrzyków. Do celów kontroli dostępu nazwa klasy wstrzykniętej jest traktowana tak, jakby była publiczną nazwą członka.

[basic.lookup]

3 Nazwa klasy wstrzykniętej jest również uważana za członka tej klasy do celów ukrywania nazw i wyszukiwania.

Ponieważ wyszukiwanie typu niekwalifikowanego typu argumentu rozpoczyna się w zakresie klasy Bar, będzie kontynuowane w zakresie jego klasy podstawowej, aby uwzględnić dowolnego członka. I znajdzie A::Foo::Foojako nazwę typu.

Jeśli chcesz użyć globalnej nazwy typu, po prostu określ ją według otaczającej (globalnej) przestrzeni nazw.

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

Który wykonuje pełne wyszukiwanie w zakresie, w którym nazwa wstrzykniętej klasy nie pojawia się.

Aby uzyskać dalsze pytanie „dlaczego”, zobacz

StoryTeller - Unslander Monica
źródło
5
@TedLyngmo - ADL dzieje się z wywołaniami funkcji, nic istotnego w tych konkretnych fragmentach.
StoryTeller - Unslander Monica
OK, czytałem i nie byłem pewien. Dzięki!
Ted Lyngmo,
3
Prowadzi to do bardzo zabawnych, struct Bar:: A::Foo::Foo::Foo::Foo::Foo {}; ale istnieją konteksty, w których A::Foo::Foowyznacza konstruktora, a zatem nie można dodawać tyle, Fooile chcesz. Jest to podobne (ale z zupełnie innego mechanizmu) z tym, że można wywołać funkcję fw następujący sposób: (************f)().
AProgrammer
@AProgrammer - Rzeczywiście. I można budować jeszcze więcej zabawnych przykładów .
StoryTeller - Unslander Monica
Ta odpowiedź z pewnością wyjaśnia „co”. Czy można to poprawić, dodając „dlaczego”? Jaki jest cel tej reguły? Które przypadki zastosowania poprawiają lub umożliwiają?
davidbak
2

Nie jest to kompletna odpowiedź, tylko kod pokazujący (ponieważ się kompiluje), Barktóry nie wchodzi do namespace A. Widać, że podczas dziedziczenia A::Foo1nie ma problemu z dwuznacznością, Fooktóra byłaby inna, gdyby to dziedziczenie pozwalało Barwejść 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
źródło