Предыдущее описание обнаруженного бага имеет забавное и в то же время познавательное продолжение. Экспериментируя с кодом, генерирующим ошибку, я видоизменил исходный пример следующим образом.
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
struct A
{
static void log( const std::string &s1,
const std::string &s2 );
static void log( const std::vector<std::string> &v,
const std::string &s );
};
void A::log( const std::string &s1, const std::string &s2 )
{
std::cout << s1 << ' ' << s2 << std::endl;
}
void A::log( const std::vector<std::string> &v,
const std::string &s )
{
std::for_each( v.begin(), v.end(),
[&s]( const std::string &text )
{ log( text, s ); } );
}
int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}
В этом примере перегруженные функции класса
A имеют имя
log, которое по моему замыслу семантически обозначает журнал для записи сообщений. Я ожидал увидеть уже знакомое сообщение об ошибке
цитата: |
error C3861: log: идентификатор не найден |
|
К моему большому удивлению компилятор выдал совершенно другое сообщение об ошибке
цитата: |
error C2661: log: нет перегруженной функции, принимающей 2 аргументов |
|
Казалось бы, что дело до перегруженных функций не должно дойти, так как, как следует из первоначального примера, компилятор сначала должен найти имя
log, а это он сделать не в состоянии, судя по тексту сообщения первой ошибки.
Я решил поменять в приведеннном примере имя
log на какое-нибудь другое имя, например,
gol.
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
struct A
{
static void gol( const std::string &s1,
const std::string &s2 );
static void gol( const std::vector<std::string> &v,
const std::string &s );
};
void A::gol( const std::string &s1, const std::string &s2 )
{
std::cout << s1 << ' ' << s2 << std::endl;
}
void A::gol( const std::vector<std::string> &v,
const std::string &s )
{
std::for_each( v.begin(), v.end(),
[&s]( const std::string &text )
{ gol( text, s ); } );
}
int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}
И получил знакомое первоначальное сообщение
цитата: |
error C3861: gol: идентификатор не найден |
|
Я использовал другие имена для перегруженных функций и всегда получал именно это сообщение об ошибке, то есть что идентификатор не найден. И только когда использовалось имя
log сообщение об ошибке становилось другим, то есть что нет перегруженной функции, принимающей два аргумента.
Какая-то мистика! Почему именно идентификатор
log является таким магическим, что компилятор изменяет именно для него текст сообщения об ошибке?!
Вчитавшись в это сообщение об ошибке, что нет перегруженной функции
log, принимающей два аргумента, мне стало любопытно: может быть тогда есть перегруженная функция
log, принимающая один аргумент?!
Подсознательно не веря, что мой эксперимент приведет к какому-нибудь осмысленному результату, который позволит понять причину исходной проблемы с изменением сообщения компилятора об ошибке при использовании перегруженной функции
log, я все же запустил на выполнение такую простенькую тестовую программу
#include "stdafx.h"
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
log( "abc" );
return 0;
}
На первый взгляд этот пример представляется очевидной глупостью! Результат предсказуем. По крайней мере покажите этот пример кода своему приятелю, программисту на С++, и тот вам уверенно скажет, что компилятор должен сообщить, что идентификатор
log не найден. Я думаю, что так ответят 10 из 10 опрошенных программистов. Они ведь знают, что 1) в заголовочном файле
iostream нет объявления такого имени; 2) если все же такое имя где-нибудь объявлено в стандартной библиотеке С++, то оно принадлежало бы стандартному пространству имен, и чтобы там было найдено это имя ему должен предшествовать префикс вложеннго имени пространства имен, то есть должно бы было быть указано в коде программы
std::log, а не просто
log.
Теперь выдержим некоторую паузу, чтобы произвести больший эффект. Хотите знать, какое собственное мнение у компилятора по этому вопросу? Хорошо. Спросим его об этом, запустив данный на наш взгляд бессмыссленный код на компиляцию.
цитата: |
error C2665: log: ни одна из 3 перегрузок не может преобразовать все типы аргументов ...microsoft visual studio 10.0\vc\include\math.h(120): может быть "double log(double)" ...microsoft visual studio 10.0\vc\include\math.h(527): или "float log(float)" ...microsoft visual studio 10.0\vc\include\math.h(575): или "long double log(long double)" |
|
Минута оцепенения, а затем возмущенный возглас: "Как это так?! Ведь имя
log в программе не было специфицировано префиксом стандартного пространства имен
std::! И почему вообще компилятор ссылается на эту функцию из заголовочного файла математической библиотеки
math.h, если тот явно нами не был подключен с помощью директивы #include, ни он сам, ни по крайней мере
cmath?! Какая-то фантасмагория!"
Давайте разбираться по порядку.
Начнем с вопроса, откуда в программе вообще оказался подключенным заголовочный файл
math.h. Если самостоятельно проверять подключенные нами явно заголовочные файлы
stdafx.h и
iostream, а в них рекурсивно проверять в свою очередь подключенные заголовочные файлы в поиске
math.h, то эта процедура очень длинная и утомительная, которая к тому же по невнимательности может привести к неверному результату. Позволим за нас это проделать компилятору. Поэтому открываему пункт главного меню "Проект". В нем выбираем пункт "свойства" проекта. Затем в появившемся диалоге выбираем пункт "С/С++", "дополнительно" и в правом окошке свойств пункта "дополнительно" указываем для пункта "показывать вложенные файлы" значение "да".
Теперь осталось лишь "построить решение" (F7), и компилятор выведет список вложенных заголовочных файлов. Начало этого списка будет выглядеть следующим образом. Чтобы сократить длинные строки, я уберу полную спецификацию файлов, оставив лишь их имена
1> Примечание: включение файла: iostream
1> Примечание: включение файла: istream
1> Примечание: включение файла: ostream
1> Примечание: включение файла: ios
1> Примечание: включение файла: xlocnum
1> Примечание: включение файла: climits
1> Примечание: включение файла: yvals.h
1> Примечание: включение файла: crtdefs.h
1> Примечание: включение файла: use_ansi.h
1> Примечание: включение файла: limits.h
1> Примечание: включение файла: crtdefs.h
1> Примечание: включение файла: cmath
1> Примечание: включение файла: math.h
Вот так! Оказывается, когда мы явно подключаем заголовок
iostream незаметно для нас подключается заголовок
math.h.
Остальное все просто. Майкрософт в своей библиотеке помещает имена, указанные в этом файле (а заодно и в
cmath) как в стандартное пространство имен, так и в глобальное пространство имен!
Так что подключая заголовок
iostream вы, не ведая того, засоряете глобальное пространство имен, что может привести к неожиданным для вас результатам.