Вот еще один пример того, когда работа программы может привести к неожидданнным для вас результатам в зависимости от того, включена ли опция компилятора
MS VC++ 2010 по использованию расширений языка или нет.
Рассмотрим следующий код.
#include "stdafx.h"
#include <iostream>
int fn() { return 0; }
void f( bool ) { std::cout << "f( bool )\n"; }
void f( void * ) { std::cout << "f( void * )\n"; }
int _tmain(int argc, _TCHAR* argv[])
{
int *p = 0;
f( p );
f( fn );
std::cout << fn << std::endl;
return 0;
}
Возникает вопрос, какая из двух перегруженных функций с именем
f будет вызвана, когда в качестве аргумента будет использовано имя функции
fn?
Допустим, вы этого не знаете, а потому просто решили проверить, какую функцию будет вызывать сгенерированный компилятором объектный код программы, запустив исходный пример на компиляцию и выполнение.
Если при этом в свойствах проекта для опции "Отключить расширения языка" у вас установлено значение "Нет", то вы получите следующий вывод на консоль:
цитата: |
f( void * ) f( void * ) 00D31113 |
|
Как из него видно, для обоих указателей, для указателя на
int и указателя на функцию
fn (а имя
fn будет преобразовано в указатель на
fn), вызывается функция
void f( void * ).
В предложении
std::cout << fn << std::endl;
также происходит преобразваоние имени
fn в указатель на эту функцию, который в свою очередь преобразуется в указатель
void *, а для типа
void * в классе потоков существует перегруженный опреатор
operator << (const void * ), который и вызывается в указанном предложении. То есть на консоль выводится адрес функции.
Итак, проверив работу кода, вы пришли к твердому убеждению, что из двух перегруженных функций, одна из которых принимает
указатель на vkid, а другая - значение типа
bool, для аргумента в виде имени функции будет вызываться первая функция. То есть указатель на функцию будет неявно преобразовываться в указатель на void.
Вы работаете со своим проектом, и работа кода вашей программы доя вас понятна и предсказуема. Но в один прекрасный день по каким-то соображениям вы решили для опции "Отключить расширения языка" установить значение "Да". Возможно, вы решили это сделать именно из тех соображений, чтобы избежать использования не соответствующей стандарту конструкции, когда временный неименованный объект связывается с неконстантной ссылкой, как это было описано в первом сообщении этой темы.
Вы естественно предполагали, что ваша программа по-прежнему будет успешно работать, так как в своем коде вы нигде не связываете временный неименнованный объект с неконстатной ссылкой. Но к вашему удивлению программа стала вести себя иначе и выдавать не тот результат, на который вы рассчитывали.
Так что же изменилось?
А изменения затронули и тот код, где выбиралась перегруженная функция, когда в качестве аргумента передавался указатель на функцию.
Давайте посмотрим, как будет работать исходный пример, если для опции "Использовать расширения языка" установить значение "Нет".
Этот результат может повергнуть в шок, не готового к такому изменению работы исходного примера программиста.
Сравним его с первым результатом. Когда в качестве аргумента для функции
f используется
указатель на int, то в обоих случаях вызывается одна и та же функция
void f( void * ).
На этом сходство заканчивается.
Для аргумента в виде
fn теперь вместо ожидаемого вызова функции, приниаающий указатель на void, вызывается уже функцию
void f( bool ).
В третьей строке вывода на консоль уже выводится не адрес функции
fn, как ранее это делалось, а значение, равное 1. Не трудно догадаться, что это выводится булево значение
true, а в соответствующей строке вместо оператора
operator <<( const void *) задействуется оператор
operator <<( bool ).
Итак, при установке опции свойств проекта "Использовать расширения языка" в значение "Нет" для аргумента в виде
fn из двух перегруженных функций
f вызывается та функция, которая имеет тип параметра
bool.
Следовательно убежденность в том, что указатель на функцию может неявно быть преобразован в
указатель на void, рушится.
Обратимся к стандарту С++. Почему для
указателя на int вызывается перегруженнная функция
void f( void * ), в то время как для
указателя на функцию вызывается перегруженная функция
void f( bool )?
В параграфе №2 раздела 4.10 Pointer conversions описываются преобразования для указателей.
цитата: |
2 A prvalue of type “pointer to cv T,” where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8) of type T (that is, not a base class subobject). The null pointer value is converted to the null pointer value of the destination type. |
|
Обратите внимание на выделенную часть текста жирным шрифтом. Такое преобразование в указатель на
void возможно для указателей на
объекты, а не на функции.
Неявное преобразование для указателей на функции описано в параграфе №1 раздела
4.12 Boolean conversions
цитата: |
1 A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true. A prvalue of type std::nullptr_t can be converted to a prvalue of type bool; the resulting value is false. |
|
В этом параграфе речь идет о любых указателях, включая и указатели на функции.
Так как
указатель на функцию не может быть неявно преобразован в
указатель на void, но может быть неявно преобразован в к типу
bool, то для имени функции
fn, задаваемого в качестве аргумента, из двух перегруженных функций
f вызывается та из них, которая имеет объявление
void f( bool ).
Это еще раз демонстрирует, насколько важно знать стандарт языка С++, а не доверяться результатам работы того или иного компилятора.