惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
MyScale Blog
MyScale Blog
量子位
WordPress大学
WordPress大学
F
Full Disclosure
V
V2EX
美团技术团队
I
InfoQ
GbyAI
GbyAI
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Security Archives - TechRepublic
Security Archives - TechRepublic
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
W
WeLiveSecurity
Google DeepMind News
Google DeepMind News
TaoSecurity Blog
TaoSecurity Blog
大猫的无限游戏
大猫的无限游戏
Project Zero
Project Zero
Spread Privacy
Spread Privacy
P
Palo Alto Networks Blog
Know Your Adversary
Know Your Adversary
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Application and Cybersecurity Blog
Application and Cybersecurity Blog
阮一峰的网络日志
阮一峰的网络日志
The Register - Security
The Register - Security
罗磊的独立博客
博客园 - 叶小钗
H
Hacker News: Front Page
N
News | PayPal Newsroom
宝玉的分享
宝玉的分享
腾讯CDC
The Cloudflare Blog
T
Threat Research - Cisco Blogs
Latest news
Latest news
博客园 - 司徒正美
T
Tailwind CSS Blog
H
Help Net Security
IT之家
IT之家
C
CXSECURITY Database RSS Feed - CXSecurity.com
NISL@THU
NISL@THU
O
OpenAI News
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy International News Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
S
Security @ Cisco Blogs
AWS News Blog
AWS News Blog
T
Tor Project blog
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
T
Threatpost

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Простой поиск имен в С++
dalerank · 2026-05-06 · via Все публикации подряд на Хабре

Простой поиск имен в С++

Уровень сложностиСложный

Время на прочтение11 мин

Охват и читатели721

Это продолжение темы начатой в статье Важны ли компилятору имена, и продолженой в Ночью все кошки серы, а using'и одинаковы, и далее в Компиляторы тоже путаются в именах. Если не читали, то лучше будет пробежаться по диагонали. Теперь вот мы подобрались к такой интересной теме, как квалифицированный и неквалифицированный поиск.

Что такое простой поиск имени n в области S? Это механизм компилятора, который находит все объявления n, находящиеся непосредственно в этой области. Просто? С виду да, но даже этот простой механизм часто работает не так как ожидает разработчик.

Например, у нас есть пространство имён N и локальная переменная N. Они могут сосуществовать вместе? Могут, потому что находятся в разных областях видимости. А пространство имён и глобальная переменная с тем же именем могут? Как же мы докатились до жизни такой, давайте разбираться.


Правильный термин из стандарта будет unqualified name lookup или неквалифицированный поиск имён, но часто используется также и "простой поиск". Прежде чем читать дальше, попробуйте сказать что будет выведено этим простым кодом. (https://godbolt.org/z/3sjjY5zbd)

namespace N {
    int value = 10;
}

int main() {
    int N = 5;
    std::cout << N << "\n";
    std::cout << N::value << "\n";
    return 0;
}
Ответ и объяснение
namespace N {
    int value = 10;
}

int main() {
    int N = 5; // локальная переменная n

    // Доступ к локальной переменной
    std::cout << "Локальная переменная N: " << N << "\n";

    // Доступ к переменной в пространстве имён N
    std::cout << "Переменная N::value: " << N::value << "\n";

    return 0;
}

Тут у нас есть пространство имён N и локальная переменная N. Они могут сосуществовать, потому что находятся в разных областях видимости, но пространство имён и глобальная переменная с тем же именем сосуществовать не могут. Локальная переменная N и пространство имён N сосуществуют без конфликта, потому что они находятся в разных областях видимости: переменная N находится в области функции main, а пространство N — в глобальной области видимости.

Если бы мы попробовали создать глобальную переменную int N, то компилятор сразу бы выдал ошибку, потому что глобальная переменная и пространство имён живут в одной области видимости и не могут иметь одно и то же имя.

namespace N {}  // пространство имён N
int N = 42;     // Ошибка! Имя уже занято

Но интереснее даже не это, а как локальная переменная и пространство имён с одним именем сосуществуют, и как именно компилятор разбирается в том, что имеется в виду. Простой поиск и квалифицированный поиск работают по совершенно разным правилам, и имя N в выражении N += 1 разрешается совсем не так как то же имя N в выражении N::x.

Теперь посмотрим другой пример, на выражение N += 1. Здесь имя N ищется с помощью простого поиска в локальной области видимости, и компилятор находит локальную переменную, и на этом поиск заканчивается. Но в выражении N::x ситуация иная, потому что оно является квалифицированным именем, и часть до :: ищется отдельно.

namespace N {
    int x = 42;
}

int main() {
    int x = 5; // локальная переменная x

    // Неквалифицированное имя: простой поиск
    x += 1;  
    std::cout << "Локальная переменная x: " << x << "\n"; 
    // Выведет 6

    // Квалифицированное имя: поиск квалификатора отдельно
    N::x += 1;
    std::cout << "Переменная N::x: " << N::x << "\n"; 
    // Выведет 43

    return 0;
}

Ключевой момент заключается в том, что при поиске имени, используемого как квалификатор, компилятор игнорирует сущности, которые не являются пространствами имён или классами, а поскольку локальная переменная N не является пространством имён, она игнорируется при таком поиске и выражение N::x  работает корректно, даже если в текущей области есть переменная N.

Такая ситуация называется скрытие имени (name hiding), когда объявление имени во внутренней области видимости делает недоступным одноимённое объявление из внешней области, и компилятор при простом поиске просто перестаёт смотреть дальше как только находит первое совпадение.

int x = 42; // глобальная переменная

int main() {
    int x = 5; // скрывает глобальную x

    std::cout << x << "\n";   // 5 локальная, глобальная скрыта
    std::cout << ::x << "\n"; // 42 явная квалификация снимает скрытие
}

То же самое происходит в классах при наследовании, и там последствия неожиданнее, потому что скрывается не одна переменная а вся перегрузка: (https://godbolt.org/z/KeM61oqe3)

struct Base {
    void foo(int x) { std::cout << "int base\n"; }
    void foo(double x) { std::cout << "double\n"; }  
    // обе перегрузки доступны
};

struct Derived : Base {
    void foo(int x) { std::cout << "int derived\n"; }  
    // скрывает ОБЕ перегрузки из Base, не только foo(int)
};

int main() {
    Derived d;
    d.foo(1);      // OK: Derived::foo(int)
    d.foo(1.0);    // ??? Base::foo(double) скрыта
    d.Base::foo(1.0); // OK: явная квалификация снимает скрытие
}

>> int derived
>> int derived
>> double

// чтобы не скрывать, нужен using:
struct Derived2 : Base {
    using Base::foo; // вводим все перегрузки Base::foo в область видимости
    void foo(int x) {} // теперь перегружает, а не скрывает
};

Это подводит нас к выводу, что скрытие имени зависит от того, какой вид поиска выполняется, скрытие работает только для того вида поиска, который рассматривает сущности данного типа. Это фундаментальная особенность C++, которая помогает избежать конфликтов между переменными, пространствами имён и типами при правильном использовании квалификаторов, что однако не всегда работало для разных компиляторов.

GCC: В старых версиях квалифицированный поиск иногда "неправильно" учитывал локальные переменные при разрешении квалификаторов, что приводило к ошибкам или неоднозначностям в коде с одинаковыми именами переменных и пространств имён. В GCC до версии 3.4 квалифицированный поиск в некоторых случаях некорректно учитывал локальные переменные при разрешении квалификатора пространства имён, что могло приводить к ошибке "N is not a namespace" даже когда локальная переменная и пространство имён должны были сосуществовать корректно. Начиная с GCC 3.4 поведение было приведено в соответствие со стандартом. 

namespace N {
    int value = 10;
}

int main() {
    int N = 5;  
    // Локальная переменная с тем же именем, 
    // что и пространство имён

    // Неквалифицированное обращение работает 
    // с локальной переменной
    N += 1;  
    std::cout << "Локальная переменная N: " << N << "\n"; 
    // Выведет 6

    // Квалифицированное имя
    // В старых версиях GCC это могло вызывать ошибку

    std::cout << "Пространство имён N::value: " << N::value << "\n";

    // Компилятор мог интерпретировать N как локальную переменную
    // и выдать ошибку "N is not a namespace" или "ambiguous"

    return 0;
}

Clang: Clang с ранних версий строго реализовал правило, что имена из зависимых базовых классов не участвуют в неквалифицированном поиске первой фазы two-phase lookup. Код с шаблонным наследованием который компилировался на GCC без this-> не компилировался на Clang, что обнаруживалось при смене тулчейна. 

// Зависимые базовые классы
// в шаблонах не участвовали в неквалифицированном поиске фазы 1

template<typename T>
struct Base {
    void foo() {}
};

template<typename T>
struct Derived : Base<T> {
    void bar() {
        foo();        
        // Clang (строго по стандарту): ERROR
        // GCC старых версий: OK искал в базе при инстанциации
        
        this->foo();  // работающий способ везде
    }
};

Компилятор мог «не заметить» квалификатор и использовать скрытое имя Derived::foo вместо Base::foo, что приводило к ошибкам или неожиданному поведению при использовании using для приведения имен базового класса в текущую область видимости.

MSVC: До VS2015 MSVC в ряде случаев не разделял корректно пространства имён и локальные переменные при разрешении квалификатора, из-за чего локальная переменная N могла мешать поиску пространства имён N в квалифицированном выражении N::f(), и компилятор выдавал ошибку "N is not a namespace" там где по стандарту должен был найти пространство имён. 

namespace N {
    void f() { std::cout << "Namespace N::f\n"; }
}

void f() { std::cout << "Global f\n"; }

int main() {
    int N = 42; 
    // Локальная переменная с именем
    // совпадающим с пространством имён

    // Неквалифицированный вызов
    f(); // вызывает глобальную f()

    // Квалифицированный вызов через пространство имён
    // В старых версиях MSVC локальная переменная N могла мешать
    // и компилятор ошибочно пытался использовать N как квалификатор
    
    N::f(); 
    // ожидалось, что вызовется N::f() из пространства имён
    // но старый MSVC мог выдать ошибку "N is not a namespace"

    return 0;
}

А поиск то совсем непростой

Ситуация становится ещё сложнее, если надо собирать множество имён из области текущего класса и всех его базовых классов, а затем выполнять поиск внутри этого множества. Когда мы используем неквалифицированное имя внутри класса или шаблона класса, компилятор не просто просматривает базовые классы по очереди, а строит множество подобъектов.

Рассмотрим уже ставший классическим пример, когда класс C наследуется от A и B. Если мы ищем имя x, компилятор найдёт x в A и x в B, но оба варианта являются допустимыми находками, и в результате возникает неоднозначность, делающая программу некорректной.

struct A {
    int x = 1;
};

struct B {
    int x = 2;
};

struct C : A, B {
    void printX() {
        // Неквалифицированное имя x встречается в 
        // обоих базовых классах
        // Компилятор не может однозначно выбрать
        // к какому x обращаться
        // Это приведёт к ошибке неоднозначности
        std::cout << x << "\n"; // Ошибка: неоднозначность
    }
};

int main() {
    C c;
    c.printX();
    return 0;
}

Квалифицированный поиск имён в C++ устроен иначе, и это делает его более предсказуемым и устойчивым к неожиданным эффектам. Когда компилятор видит имя с квалификатором (C::x), то он запускает строго определенный набор правил, в котором точно задан порядок шагов.

Прежде всего, компилятор ищет явно объявленные имена непосредственно в указанной области и если мы пишем C::x, то в первую очередь рассматриваются все объявления x, которые действительно принадлежат C. Это могут быть статические члены класса, вложенные типы, перечисления или функции, объявленные прямо внутри C. Если такое объявление найдено, поиск считается завершённым и никакие внешние механизмы расширения видимости на этом этапе уже не играют роли.

// Глобальная функция с тем же именем не должна мешать
void x() { printf("global x()\n"); }

namespace N {
    void x() { printf("N::x()\n"); }
}

struct C {
    static int x;
};

int C::x = 42;

int main() {
    // Квалифицированный поиск: смотрим только внутри C
    printf("%d\n", C::x);         // ищет только в C, не привлекая в анализ x() и N:x()
}

Для C::x  компилятор смотрит только в C и находит static int x = 42. Глобальная функция x() и N::x() не рассматриваются вообще, хотя при неквалифицированном поиске они тоже были бы кандидатами. Рассмотрим другой пример:

struct A {
    int x = 42;
};

struct B : A {};  // B наследует x из A
struct C : A {};  // C наследует x из A

struct E : B, C { // E наследует и от B и от C
    void foo() {
        // x = 1; // ошибка компиляции!
        // error: member 'x' found in multiple base classes of different types
    }
};

На первый взгляд кажется, что никакой проблемы быть не должно. В конечном итоге член x объявлен только в A, и никакого второго x в B или C нет. Однако когда компилятор ищет неквалифицированное имя x внутри E, он не просто «ищет объявление», а строит множество подобъектов базовых классов. В этом множестве оказываются:

  • A, как прямой базовый класс C

  • A, как базовый класс B, который сам является базовым классом A

Да, это одно и то же объявление A::x, но пути, по которым к нему можно прийти, различны. Для компилятора это означает, что поиск был успешен двумя независимыми маршрутами, и ни один из них не является предпочтительным. В результате возникает неоднозначность, и программа считается некорректной и приводит нас к пониманию, что поиск в базовых классах имеет два уровня: важно не только то, какое объявление найдено, но и то, каким путём к нему пришёл компилятор. (https://godbolt.org/z/qEsrET84v)

// Компилятор строит граф объектов:
//
//    A::x     A::x
//      |       |
//      B       C
//       \     /
//          E
//
// Два пути к одному и тому же объявлению A::x:
// путь 1: E -> B -> A::x
// путь 2: E -> C -> A::x
//
// Компилятор видит два маршрута и не может выбрать предпочтительный

int main() {
    E e;

    // явная квалификация устраняет неоднозначность:
    e.B::x = 1;  // OK: явно указываем путь через B
    e.C::x = 2;  // OK: явно указываем путь через C

    // это два РАЗНЫХ подобъекта A в памяти:
    std::cout << e.B::x << "\n"; // 1
    std::cout << e.C::x << "\n"; // 2
}

Далее, если в самой области C подходящего имени не обнаружено, то компилятор переходит к следующему шагу и начинает учитывать имена, которые стали видимыми в этой области через директивы using namespace. Тут надо напомнить, что речь идёт именно о директивах, а не о using-объявлениях, “поэтому имена могут быть найдены квалифицированным поиском, но исключительно как запасной вариант, когда собственных объявлений в этой области нет” - "Правило N-объявлений" (пример чуть ниже). Устоявшегося термина для этого правила в стандарте нет, и часто оно описывается через механизм, а не через отдельное название. В стандарте C++ соответствующий параграф будет [namespace.qual], а само правило формулируется так:

Если при квалифицированном поиске в области X собственных объявлений не найдено, то рассматриваются имена из пространств имён, введённых через using namespace директивы в X, получая таким образом «набор связанных пространств имён» (associated namespaces).

Собственно это и есть основное отличие квалифицированного поиска от неквалифицированного, когда при неквалифицированном поиске using namespace активно участвуют в формировании множества кандидатов с самого начала, что часто приводит к неожиданным конфликтам и неоднозначностям. Квалифицированный поиск, напротив, жёстко приоритизирует явные объявления, и только при их отсутствии расширяет область поиска за счёт using namespace, что делает его значительно более стабильным и защищённым от случайных изменений контекста.

Небольшое отступление, чтобы пример был полным. Вспомним про виртуальное наследование, которое устраняет это поведение (но несет с собой много других проблем), потому что теперь существует только один подобъект A: (https://godbolt.org/z/P9qbqaGPs)

struct A {
    int x = 42;
};

struct B : virtual A {};  // виртуальное наследование
struct C : virtual A {};  // виртуальное наследование

struct E : B, C {
    void foo() {
        x = 1;  // OK: только один A в иерархии
    }
};

// граф подобъектов теперь выглядит иначе:
//
//      B     C
//       \   /
//         A      <-- один экземпляр, один путь
//
// нет неоднозначности

int main() {
    E e;
    e.x = 1;       // OK
    e.B::x = 1;    // OK, то же самое x
    e.C::x = 1;    // OK, то же самое x
    // все три обращаются к одному и тому же подобъекту
}

Без виртуального наследования E содержит два физически разных подобъекта A в памяти, и компилятор не может угадать к которому из них обращаться при неквалифицированном поиске x, даже если оба содержат одно и то же объявление.

Представим теперь, что у нас есть два пространства имён. В одном из них объявлено имя, которое мы будем «подмешивать» с помощью using namespace, а в другом тоже собственное объявление с тем же именем.

namespace N {
    int x = 42;
}

namespace C {
    using namespace N;
    int x = 10;
}

Может показаться, что внутри C теперь существует два возможных x: один собственный (C::x), а второй пришёл из N через using namespace, но квалифицированный поиск сразу расставляет всё по местам. Теперь, если мы напишем:

int a = C::x;

то компилятор выполняет квалифицированный поиск для имени x только в области C и сначала ищет явно объявленные имена в C,  там находит int x = 10;, и на этом поиск останавливается. Второе имя N::x, несмотря на директиву using namespace N, даже не рассматривается. В результате a получает значение 10. Теперь посмотрим на контрпример, когда выполняется неквалифицированный поиск внутри самой области C: (https://godbolt.org/z/q1bK5cEG1)

namespace N {
    int x = 42;
}

namespace C {
    using namespace N;
    int x = 10;
}

int f() {
    using namespace C;
    return x;
}

int main() {
    std::cout << f();
}

<source>:15:12: error: reference to 'x' is ambiguous
   15 |     return x;
      |            ^
<source>:10:9: note: candidate found by name lookup is 'C::x'
   10 |     int x = 10;
      |         ^
<source>:5:9: note: candidate found by name lookup is 'N::x'
    5 |     int x = 42;
      |         ^

Здесь имя x используется без квалификатора, поэтому применяется неквалифицированный поиск, который собирает кандидатов из текущей области и из пространств имён, подключённых через using namespace. В этом случае в множестве кандидатов окажутся и C::x, и N::x, а так как оба объявления имеют одинаковую «силу», то программа становится неоднозначной, и компилятор выдает ошибку.

Теперь вернёмся к квалифицированному поиску и немного изменим пример: (https://godbolt.org/z/P9qbqaGPs)

namespace N {
    int x = 42;
}

namespace C {
    using namespace N;
}

int main() {
    int a = C::x; // Но в C нет собственного x!
     std::cout << "Переменная N::x: " << C::x << "\n";
    // выведет 42
}

Здесь ситуация иная, теперь в пространстве имён C нет явно объявленного x, поэтому квалифицированный поиск сначала пытается найти x непосредственно в C, но терпит неудачу и после этого рассматривает имена, пришедшие через using namespace N, и находит там N::x. 

Что дальше...

Один из примеров выше показал, что скрытие имён в классах работает не так, как это интуитивно ожидается. Объявление в Derived скрывает все перегрузки из Base, а не только совпадающую и такое поведение является прямым следствием того, как работает поиск имён по иерархии базовых классов.

Именно для таких случаев существует using-объявление, позволяя явно ввести имена из базового класса в область производного, не нарушая правил поиска и не создавая конфликтов. Но using это не только про наследование, к С++14 он стал полноценным инструментом управления видимостью, который работает по своим правилам и взаимодействует с поиском имён предсказуемым и строго определённым образом, и об этом я расскажу в следующей статье...

Уже набралось материала еще для двух глав (история концептов и поиск имен), выложу их на гитхабе в ближайшее время https://github.com/dalerank/playful_programming_cpp