Просматривая материалы конференции GoingNative 2012 (которую всем программистам на С++ очень советую посмотреть), я обратил внимание на один пример кода:
Сможете ли вы, не подглядывая в ответ, сказать, что напечатает эта программа и самое главное, почему?
Под катом — предположение разработчика Clang из Google о том, почему этот код работает так, как он работает. Еще раз, кто не уловил: разработчик компилятора С++ из Google не знает этого точно, у него всего-лишь есть предположение.
При компиляции в соответствии со стандартом С++98 этот код напечатает "X!", при компиляции в соответствии со стандартом С++11 этот код напечатает "Pointer!".
Посмотрим внимательно на строку
Как видим, здесь создаётся экземпляр структуры S. У неё нет явного конструктора, а значит вызывается конструктор по-умолчанию. И вот тут выходит на сцену стандарт С++11 с его продвинутой поддержкой константных выражений (Generalized constant expressions). Любая функция (в том числе конструктор) в С++11 может быть объявлена как всегда возвращающая одно и то же выражение. Это сделано для возможности написания вот такого кода:
Константные выражения используются в объявлениях массивов, в перечислениях (enums), в блоках switch\case. И компилятор С++11 старается любую функцию, которая может быть константной, считать именно константной, дабы иметь возможность использовать её во всех этих местах. А что же конструктор структуры S? Ну, если он будет присваивать переменной n всегда какое-то определенное число (а стандарт этого не запрещает) — значит он тоже может быть константным выражением. А с чего бы ему присваивать n каждый раз разные значения? Присваивает ноль. Почему именно ноль? А у вас есть какое-то более умное значение на уме?
А значит, вышеуказанная строка равнозначна:
Ну а это, как мы знаем, полностью равнозначно:
А это преобразуется скорее к void*, чем к struct X (даже с соответствующим конструктором X(int)). И вот мы имеем в явном виде вызов void f(void*)! Ну и печатается "Pointer!".
Почему же это не происходит в компиляторе с поддержкой С++98? Да потому что у него нет этой самой продвинутой поддержки константных выражений. У дефолтного конструктора структуры S нет никаких причин присваивать свойству n значение ноль (стандарт не требует этого). Ну вот он этого и не делает. А значит строка f(S().n); не может быть однозначно преобразована в f(0); со всеми отсюда вытекающими последствиями.
Стандарт С++11 новый, его поддержка в компиляторах тоже еще сыровата. Будьте готовы к подобным сюрпризам.
#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), в блоках switch\case. И компилятор С++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 новый, его поддержка в компиляторах тоже еще сыровата. Будьте готовы к подобным сюрпризам.