Необходимо пояснить особенности работы пространства имён в С++

Есть программа, которая состоит из трёх файлов:

Названия 2-го и 3-го файлов условны

  1. sales.h - содержит описание структуры, определение символической константы и 3 прототипа функций

  2. main.cpp - файл с основной функцией main()

  3. sales.cpp - файл с реализациями 3 функций из sales.h

В sales.h всё определено в пространстве имён под названием SALES

Вопрос следующий:


Почему в main.cpp, при использовании using к, например структуре Sales (using SALES::Sales), все функции будут доступны без указания пространства имён

Я же не применял using к этим функциям или всему пространству имён, каким образом они доступны для использования?

Вот код:


  1. main.cpp:
#include "sales.h"

int main()
{
    using SALES::QUARTERS;
    using SALES::Sales;

    Sales n_inter;
    Sales inter;

    double ar[QUARTERS] = { 10.1, 12.22,23.11,223.4 };
    setSales(n_inter, ar, QUARTERS); // Почему функция доступна?
    showSales(n_inter);              // Почему функция доступна?
    setSales(inter);                 // Почему функция доступна?
    showSales(inter);                // Почему функция доступна?
    
    return 0;
}

  1. sales.h:
#pragma once

namespace SALES
{

const int QUARTERS = 4;

struct Sales
{
    double sales[QUARTERS];
    double average;
    double max;
    double min;
};

void setSales(Sales& s, const double ar[], int n); 
void setSales(Sales& s);
void showSales(const Sales& s); 

}

  1. sales.cpp:
#include "sales.h"
#include <iostream>
        
namespace SALES
{

void setSales(Sales& s, const double ar[], int n)
{
    for (int i = 0; i < n; i++) // copies 4 items to the sales member of s
        s.sales[i] = ar[i];
    
    s.max = s.sales[0];
    s.average = s.sales[0];
    
    for (int i = 1; i < n; i++) // computes the maximum and average
    {
        s.average += s.sales[i];
        if (s.max < s.sales[i])
            s.max = s.sales[i];
    }

    s.average /= n; // average
    s.min = s.sales[0];
    
    for (int i = 1; i < n; i++) // computes the minimum
    {
        if (s.min > s.sales[i])
            s.min = s.sales[i];
    }
}

void setSales(Sales& s)
{
    for (int i = 0; i < QUARTERS; i++)
    {
        std::cout << "Enter total sales for " << i + 1 << " quarters: ";
        std::cin >> s.sales[i];
    }
    
    s.max = s.sales[0];
    s.average = s.sales[0];
    
    for (int i = 1; i < QUARTERS; i++) // computes the maximum and average
    {
        s.average += s.sales[i];
        if (s.max < s.sales[i])
            s.max = s.sales[i];
    }
    s.average /= QUARTERS; // average
    s.min = s.sales[0];
    
    for (int i = 1; i < QUARTERS; i++) // computes the minimum
    {
        if (s.min > s.sales[i])
            s.min = s.sales[i];
    }
}

void showSales(const Sales& s)
{
    std::cout << "Average: " << s.average << std::endl;
    std::cout << "Maximum: " << s.max << std::endl;
    std::cout << "Mininum: " << s.min << std::endl;
}

}

Ответы (1 шт):

Автор решения: LolPopGames

Спасибо @Harry за источники! В данном ответе я просто собрал информацию в красивый ответ. Вот источники: [1] (не использовался), [2], [3]

Всё дело в Поиске Кёнига, разработанный одноимённым програмистом

Цитирую из википедии (немного упростив):


Поиск Кёнига - это формальный набор правил C++, для поиска необъявленных имен функций и операторов при их вызове, включая перегрузку функций, и функции, объявленные в пространствах имён

Конкретно в вашей проблеме оператор using здесь вообще ни при чем

Возьмём пример (почти как у вас):

#include <iostream>

namespace A {
    struct SomeClass {};

    void func(const SomeClass &value) {
    std::cout << "Hello from A::func!" << std::endl;
    }
}

int main(int argc, char *argv[]) {
    func(A::SomeClass{});
    return 0;
}

Данный код прекрасно скомпилируется, из-за Поиска Кёнига

Хотя Поиск Кёнига довольно сложен, здесь его просто объяснить:

Цитирую из поста на сайте otus:


При разрешении вызова функции, список кандидатов составляется не только из элементов, доступных в данном пространстве имён, но и в пространствах имён аргументов вызова

Т.е., простыми словами, когда мы передаем объект из какого-то пространства имён в параметры функции, компилятор будет искать функцию в текущем пространстве имён (глобальном), а также в пространстве имён параметров (в данном случае A)

Зачем это сделано?


Возьмём пример когда мы используем Поиск Кёнига

std::cout << "Hello, World" << std::endl;

Казалось бы, что же тут такого?

Каждый программист на C++ довольно часто пользуется оператором operator<< для вывода в std::cout

Однако, в глобальном пространстве имён такого оператора нет!

Он есть только в пространстве имён std

И если бы Поиска Кёнига не было, то пришлось бы писать так:

std::operator<<(std::operator<<(std::cout, "Hello, World!"), "\n");

Это был один из мотивов внедрения такого набора правил

Почему ваш код работает так?


Собственно, это и есть работа Поиска Кёнига

Вы передаёте SALES::QUARTERS или экземпляры структуры SALES::Sales, что и приводит к расширению поиска имён функций

→ Ссылка