Столкновение пространства имен C ++ в конструкторе копирования

33

У меня есть следующий код:

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

struct Foo {
    int b;
};

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

Компиляторы C ++ жалуются на «c = foo.b», потому что A :: Foo не имеет члена с именем b. Если я изменяю тип параметра Bar с помощью :: Foo, он работает.

Мой вопрос заключается в том, что является рациональным для такого поведения (я полагаю, это связано с тем, что наследование заставляет Bar войти в пространство имен A, но я не могу найти какую-либо документацию, подтверждающую эту теорию.

Винсент Ле Лижур
источник
8
Я думаю, что это зависит от аргумента поиска. Я пометил «язык-юрист», так как я думаю, что вы после ответов, которые ссылаются на языковой стандарт. И очень хороший первый вопрос! Делает все это стоящим.
Вирсавия
Он не входит в пространство имен A, которое вы можете увидеть, если разрешите Barнаследовать от другой структуры в A. Тогда нет никакой двусмысленности. Это больше похоже на наследование добавляет все от, A::Fooв Barтом числе разрешение Fooна A::Foo. Извините, я не могу выразить это более точно.
n314159
@Bathsheba Вы имеете в виду поиск имени зависимого типа аргумента для поиска имен функций (или имен шаблонов функций) или зависимых имен в шаблонах?
любопытный парень

Ответы:

22

Каждому классу присваивается имя в качестве члена. Так что вы можете назвать A::Foo::Foo. Это называется введенным именем класса.

[учебный класс]

2 Имя класса вставляется в область, в которой оно объявляется сразу после того, как имя класса замечено. Имя класса также вставляется в область действия самого класса; это известно как имя введенного класса. В целях проверки доступа имя введенного класса обрабатывается так, как если бы оно было публичным именем члена.

[Basic.lookup]

3 Inject-class-name класса также считается членом этого класса в целях сокрытия имени и поиска.

Поскольку поиск по имени без аргументов типа аргумента начинается в области действия класса Bar, он будет продолжаться в области действия своего базового класса для учета любого члена там. И он найдет A::Foo::Fooкак имя типа.

Если вы хотите использовать глобальное имя типа, просто определите его по окружающему (глобальному) пространству имен.

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

Который выполняет полностью квалифицированный поиск в области, где введенное имя класса не появляется.

Дополнительный вопрос «почему» см.

Рассказчик - Unslander Monica
источник
5
@TedLyngmo - ADL происходит с вызовами функций, ничего особенного в этих конкретных отрывках.
StoryTeller - Unslander Моника
Оки, я читал и не был уверен. Спасибо!
Тед Люнгмо
3
Это приводит к очень забавному, struct Bar:: A::Foo::Foo::Foo::Foo::Foo {}; но есть контексты, в которых A::Foo::Fooуказывается конструктор, и поэтому вы не можете продолжать добавлять столько, Fooсколько хотите. Это похоже (но с совершенно другой механизм) с тем , что вы можете вызвать функцию fследующим образом: (************f)().
AProgrammer
@AProgrammer - Действительно. И можно построить еще более забавные примеры .
StoryTeller - Unslander Моника
Этот ответ, безусловно, объясняет «что». Можно ли улучшить, чтобы добавить «почему»? Как, в чем цель этого правила? Какие варианты использования это улучшает или делает возможным?
Давидбак
2

Не полный ответ, только код, который показывает (поскольку он компилируется), Barно не вводит namespace A. Вы можете видеть, что при наследовании от A::Foo1нет проблем с неоднозначностью, Fooкоторая была бы другой, если бы это наследство позволяло Barвойти 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
источник