On-line: гостей 0. Всего: 0 [подробнее..]
Программисты всех стран, объединяйтесь!

АвторСообщение



ссылка на сообщение  Отправлено: 28.07.12 19:21. Заголовок: MS VC++ 2010: баг в лямбда-выражении при поиске перегруженной функции


Еще один неочевидный баг обнаружился в MS VC++ 2010 при использовании лямбда-выражений.

Рассмотрим такой простой код

#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;
}


Обратите внимание на вызов функции g- члена класса A в лямбда-выражении, используемом в теле функции f.. В этом вызове указывается неквалифицированное имя g: g(x );

Этот код успешно компилируется в MS VC++ 2010, и, как и полагается, на экран консоли выводится следующее


 цитата:
0 1 2 3 4 5 6 7 8 9
Для продолжения нажмите любую клавишу . . .



Теперь слегка изменим код программы, заменив в нем лишь имя функции g на имя f. Таким образом мы получаем в классе A. две перегруженные функции, одна из которых принимает константную ссылку на std::vector<int>, а другая с тем же именем принимает значение типа int.

#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;
}


Увы, этот код уже не компилируется MS VC++ 2010! Компилятор указывает, что в строке, где происходит вызов f( x ) внтури тела лямбда-выражения имя f не найдено! Вот сообщение компилятора:


 цитата:
error C3861: f: идентификатор не найден



Если же сделать имя квалифицированным, то есть в теле лямбда-выражения вместо f( x ) написать A::f( x ), то программа успешно компилируется и выполняется компилятором MS VC++ 2010.

Я не вижу причин, по которым компилятор не должен найти перегруженное неквалифицированное имя f в теле лямбда-выражения. На другом, онлайновом компиляторе, такой проблемы не возникает.

Поэтому сообщение о данном баге MS VC++ 2010 я отправил в Майкрософт, чтобы получить от них разъяснение по этому вопросу.

Спасибо: 0 
ПрофильЦитата Ответить
Ответов - 2 [только новые]





ссылка на сообщение  Отправлено: 28.07.12 19:23. Заголовок: Предыдущее описание ..


Предыдущее описание обнаруженного бага имеет забавное и в то же время познавательное продолжение. Экспериментируя с кодом, генерирующим ошибку, я видоизменил исходный пример следующим образом.

#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 вы, не ведая того, засоряете глобальное пространство имен, что может привести к неожиданным для вас результатам.


Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 28.07.12 19:24. Заголовок: Пришел ответ от Майк..


Пришел ответ от Майкрософт, подтверждающий, что действительно обнаружен баг. По заверениям Майкрософт исправление этого бага уже включено в исходный код компилятора и будет доступно пользователям с выходом очередного релиза компилятора.


 цитата:
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



Спасибо: 0 
ПрофильЦитата Ответить
Ответ:
1 2 3 4 5 6 7 8 9
большой шрифт малый шрифт надстрочный подстрочный заголовок большой заголовок видео с youtube.com картинка из интернета картинка с компьютера ссылка файл с компьютера русская клавиатура транслитератор  цитата  кавычки моноширинный шрифт моноширинный шрифт горизонтальная линия отступ точка LI бегущая строка оффтопик свернутый текст

показывать это сообщение только модераторам
не делать ссылки активными
Имя, пароль:      зарегистрироваться    
Тему читают:
- участник сейчас на форуме
- участник вне форума
Все даты в формате GMT  3 час. Хитов сегодня: 25
Права: смайлы да, картинки да, шрифты да, голосования нет
аватары да, автозамена ссылок вкл, премодерация откл, правка нет