Просматривая материалы конференции GoingNative 2012 (которую всем программистам на С++ очень советую посмотреть), я обратил внимание на один пример кода:
#include <iostream> struct S { int n; }; struct X { X(int) {} }; void f(void*) { std::cerr << "Pointer!n"; } void f(X) { std::cerr << "X! n"; } int main() { f(S().n); }
Сможете ли вы, не подглядывая в ответ, сказать, что напечатает эта программа и самое главное, почему?
Под катом — предположение разработчика Clang из Google о том, почему этот код работает так, как он работает. Еще раз, кто не уловил: разработчик компилятора С++ из Google не знает этого точно, у него всего-лишь есть предположение.
Ответ
При компиляции в соответствии со стандартом С++98 этот код напечатает "X!", при компиляции в соответствии со стандартом С++11 этот код напечатает "Pointer!".
# clang++ -std=c++98 -g -o cxx11-4 cxx11-4.cpp # ./cxx11-4 X! # clang++ -std=c++11 -g -o cxx11-4 cxx11-4.cpp # ./cxx11-4 Pointer!
Вопрос к разработчикам стандарта С++11
Пояснения
Посмотрим внимательно на строку
f(S().n);
Как видим, здесь создаётся экземпляр структуры S. У неё нет явного конструктора, а значит вызывается конструктор по-умолчанию. И вот тут выходит на сцену стандарт С++11 с его продвинутой поддержкой константных выражений (Generalized constant expressions). Любая функция (в том числе конструктор) в С++11 может быть объявлена как всегда возвращающая одно и то же выражение. Это сделано для возможности написания вот такого кода:
int get_five() {return 5;} int some_value[get_five() + 7];
Константные выражения используются в объявлениях массивов, в перечислениях (enums), в блоках switchcase. И компилятор С++11 старается любую функцию, которая может быть константной, считать именно константной, дабы иметь возможность использовать её во всех этих местах. А что же конструктор структуры S? Ну, если он будет присваивать переменной n всегда какое-то определенное число (а стандарт этого не запрещает) — значит он тоже может быть константным выражением. А с чего бы ему присваивать n каждый раз разные значения? Присваивает ноль. Почему именно ноль? А у вас есть какое-то более умное значение на уме?
А значит, вышеуказанная строка равнозначна:
f(0);
Ну а это, как мы знаем, полностью равнозначно:
f(NULL);
А это преобразуется скорее к void*, чем к struct X (даже с соответствующим конструктором X(int)). И вот мы имеем в явном виде вызов void f(void*)! Ну и печатается "Pointer!".
Почему же это не происходит в компиляторе с поддержкой С++98? Да потому что у него нет этой самой продвинутой поддержки константных выражений. У дефолтного конструктора структуры S нет никаких причин присваивать свойству n значение ноль (стандарт не требует этого). Ну вот он этого и не делает. А значит строка f(S().n); не может быть однозначно преобразована в f(0); со всеми отсюда вытекающими последствиями.
Вывод
Стандарт С++11 новый, его поддержка в компиляторах тоже еще сыровата. Будьте готовы к подобным сюрпризам.
Автор: