Форум » C/C++ » MS VC++ 2010: баг в лямбда-выражении при поиске перегруженной функции » Ответить

MS VC++ 2010: баг в лямбда-выражении при поиске перегруженной функции

Сыроежка: Еще один неочевидный баг обнаружился в MS VC++ 2010 при использовании лямбда-выражений. Рассмотрим такой простой код [pre2]#include "stdafx.h" #include <iostream> #include <vector> #include <algorithm> #include <numeric> struct A { static void f( const std::vector<int> & ); static void g( int ); }; void A::f( const std::vector<int> &v ) { std::for_each( v.begin(), v.end(), []( int x ) { g( x ); } ); std::cout << std::endl; } void A::g( int x ) { std::cout << x << ' '; } int _tmain(int argc, _TCHAR* argv[]) { std::vector<int> v( 10 ); std::iota( v.begin(), v.end(), 0 ); A::f( v ); return 0; }[/pre2] Обратите внимание на вызов функции g- члена класса A в лямбда-выражении, используемом в теле функции f.. В этом вызове указывается неквалифицированное имя g: g(x ); Этот код успешно компилируется в MS VC++ 2010, и, как и полагается, на экран консоли выводится следующее [quote]0 1 2 3 4 5 6 7 8 9 Для продолжения нажмите любую клавишу . . .[/quote] Теперь слегка изменим код программы, заменив в нем лишь имя функции g на имя f. Таким образом мы получаем в классе A. две перегруженные функции, одна из которых принимает константную ссылку на std::vector<int>, а другая с тем же именем принимает значение типа int. [pre2]#include "stdafx.h" #include <iostream> #include <vector> #include <algorithm> #include <numeric> struct A { static void f( const std::vector<int> & ); static void f( int ); }; void A::f( const std::vector<int> &v ) { std::for_each( v.begin(), v.end(), []( int x ) { f( x ); } ); std::cout << std::endl; } void A::f( int x ) { std::cout << x << ' '; } int _tmain(int argc, _TCHAR* argv[]) { std::vector<int> v( 10 ); std::iota( v.begin(), v.end(), 0 ); A::f( v ); return 0; }[/pre2] Увы, этот код уже не компилируется MS VC++ 2010! Компилятор указывает, что в строке, где происходит вызов f( x ) внтури тела лямбда-выражения имя f не найдено! Вот сообщение компилятора: [quote]error C3861: f: идентификатор не найден[/quote] Если же сделать имя квалифицированным, то есть в теле лямбда-выражения вместо f( x ) написать A::f( x ), то программа успешно компилируется и выполняется компилятором MS VC++ 2010. Я не вижу причин, по которым компилятор не должен найти перегруженное неквалифицированное имя f в теле лямбда-выражения. На другом, онлайновом компиляторе, такой проблемы не возникает. Поэтому сообщение о данном баге MS VC++ 2010 я отправил в Майкрософт, чтобы получить от них разъяснение по этому вопросу.

Ответов - 2

Сыроежка: Предыдущее описание обнаруженного бага имеет забавное и в то же время познавательное продолжение. Экспериментируя с кодом, генерирующим ошибку, я видоизменил исходный пример следующим образом. [pre2]#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; }[/pre2] В этом примере перегруженные функции класса A имеют имя log, которое по моему замыслу семантически обозначает журнал для записи сообщений. Я ожидал увидеть уже знакомое сообщение об ошибке error C3861: log: идентификатор не найден К моему большому удивлению компилятор выдал совершенно другое сообщение об ошибке error C2661: log: нет перегруженной функции, принимающей 2 аргументов Казалось бы, что дело до перегруженных функций не должно дойти, так как, как следует из первоначального примера, компилятор сначала должен найти имя log, а это он сделать не в состоянии, судя по тексту сообщения первой ошибки. Я решил поменять в приведеннном примере имя log на какое-нибудь другое имя, например, gol. [pre2]#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; }[/pre2] И получил знакомое первоначальное сообщение error C3861: gol: идентификатор не найден Я использовал другие имена для перегруженных функций и всегда получал именно это сообщение об ошибке, то есть что идентификатор не найден. И только когда использовалось имя log сообщение об ошибке становилось другим, то есть что нет перегруженной функции, принимающей два аргумента. Какая-то мистика! Почему именно идентификатор log является таким магическим, что компилятор изменяет именно для него текст сообщения об ошибке?! Вчитавшись в это сообщение об ошибке, что нет перегруженной функции log, принимающей два аргумента, мне стало любопытно: может быть тогда есть перегруженная функция log, принимающая один аргумент?! Подсознательно не веря, что мой эксперимент приведет к какому-нибудь осмысленному результату, который позволит понять причину исходной проблемы с изменением сообщения компилятора об ошибке при использовании перегруженной функции log, я все же запустил на выполнение такую простенькую тестовую программу [pre2]#include "stdafx.h" #include <iostream> int _tmain(int argc, _TCHAR* argv[]) { log( "abc" ); return 0; }[/pre2] На первый взгляд этот пример представляется очевидной глупостью! Результат предсказуем. По крайней мере покажите этот пример кода своему приятелю, программисту на С++, и тот вам уверенно скажет, что компилятор должен сообщить, что идентификатор 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), и компилятор выведет список вложенных заголовочных файлов. Начало этого списка будет выглядеть следующим образом. Чтобы сократить длинные строки, я уберу полную спецификацию файлов, оставив лишь их имена [pre2]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[/pre2] Вот так! Оказывается, когда мы явно подключаем заголовок iostream незаметно для нас подключается заголовок math.h. Остальное все просто. Майкрософт в своей библиотеке помещает имена, указанные в этом файле (а заодно и в cmath) как в стандартное пространство имен, так и в глобальное пространство имен! Так что подключая заголовок iostream вы, не ведая того, засоряете глобальное пространство имен, что может привести к неожиданным для вас результатам.

Сыроежка: Пришел ответ от Майкрософт, подтверждающий, что действительно обнаружен баг. По заверениям Майкрософт исправление этого бага уже включено в исходный код компилятора и будет доступно пользователям с выходом очередного релиза компилятора. Posted by Microsoft on 16.03.2012 at 16:08 Hi: A fix for this issue has been checked into the compiler sources. The fix should show up in the next release of Visual C++. Xiang Fan Visual C++ Team



полная версия страницы