Предлагается совершенно невинный на вид кусок кода на C++. Здесь нет ни шаблонов, ни виртуальных функций, ни наследования, но создатели этого чудесного языка спрятали грабли посреди чистa поля.
Вопрос: какой тип у переменной b? Совсем не тот, который можно было бы предположить на первый взгляд.
Конечно же, тип переменной b не B, иначе бы не было этой статьи :) Я не буду сразу приводить ответ, а вместо этого расскажу, как до него можно дойти, не копаясь в тысячестраничном стандарте.
Для начала добавим немного отладочной печати:
Если попробовать запустить этот код, окажется, что вообще ничего не выводится. Но если заменить строку (1) на
внезапно всё начинает работать.
А теперь посмотрим внимательно на вывод компилятора при максимально включенных предупреждениях
С первыми двумя строками всё понятно, действительно параметры конструкторов не используются. А вот последняя строка выглядит очень странно. Как переменная i оказалась неиспользуемой, если она используется в следующей строке?
В принципе, этой информации достаточно, чтобы, немного подумав, ответить на поставленный вопрос. Но если умные мысли в голову не приходят, и хочется ещё немного поприключаться, почему бы просто не спросить компилятор? На помощь приходит RTTI.
При компиляции GCC 4.3 результатом выполнения этой программы является строка
в которой зашифрована нужная нам информация о типе переменной (конечно, другой компилятор выдаст другую строку, формат вывода type_info::name() в стандарте не описан и оставлен на усмотрение разработчика). Узнать же, что означают эти буквы и цифры, нам поможет c++filt.
Вот и ответ: это функция, принимающая на вход параметр типа A и возвращающая значение типа B.
Осталось понять, почему наша строка проинтерпретировалась таким неожиданным способом. Всё дело в том, что в объявлении типа переменной лишние скобки вокруг имени игнорируются. Например, мы можем написать
и это будет означать в точности тоже самое, что
Поэтому многострадальную строку (1) можно без изменения смысла переписать, убрав лишнюю пару скобок:
Теперь невооружённым взглядом видно, что b это объявление функции с одним аргументом типа A, которая возвращает значение типа B.
Заодно мы объяснили странный ворнинг о неиспользованной переменной i — действительно, она не имеет никакого отношения к формальному параметру i.
Нам осталось только объяснить компилятору, что же на самом деле мы от него хотим — то есть, получить переменную типа B, проинициализированную переменной типа A. Самый простой способ — добавить лишних скобок, вот так:
или так:
Этого достаточно, чтобы убедить парсер, что это не объявление функции.
Как альтернативу, можно использовать форму вызова конструктора с помощью присваивания, если только конструктор не объявлен explicit:
Несмотря на наличие знака '=', никакого лишнего копирования здесь не происходит, в чём можно легко убедиться, заведя в классе B приватный конструктор копирования.
А можно просто ввести дополнительную переменную:
Правда, при этом потребуется лишнее копирование переменной a, но во многих случаях это приемлемо.
Выберите тот способ, который кажется вам более понятным :)
Навеяно постом в StackOverflow
P.S. Спасибо sse за уточнения.
struct A {
A (int i) {}
};
struct B {
B (A a) {}
};
int main () {
int i = 1;
B b(A(i)); // (1)
return 0;
}
* This source code was highlighted with Source Code Highlighter.
Вопрос: какой тип у переменной b? Совсем не тот, который можно было бы предположить на первый взгляд.
Анализ
Конечно же, тип переменной b не B, иначе бы не было этой статьи :) Я не буду сразу приводить ответ, а вместо этого расскажу, как до него можно дойти, не копаясь в тысячестраничном стандарте.
Для начала добавим немного отладочной печати:
#include <iostream>
struct A {
A (int i) { std::cout << 'A';}
};
struct B {
B (A a) { std::cout << 'B';}
};
int main () {
int i = 1;
B b(A(i)); // (1)
return 0;
}
* This source code was highlighted with Source Code Highlighter.
Если попробовать запустить этот код, окажется, что вообще ничего не выводится. Но если заменить строку (1) на
B b(A(1));
внезапно всё начинает работать.
А теперь посмотрим внимательно на вывод компилятора при максимально включенных предупреждениях
$ g++ -W -Wall test.cpp
x.cpp:2: warning: unused parameter ‘i’
x.cpp:6: warning: unused parameter ‘a’
x.cpp: In function ‘int main()’:
x.cpp:10: warning: unused variable ‘i’
С первыми двумя строками всё понятно, действительно параметры конструкторов не используются. А вот последняя строка выглядит очень странно. Как переменная i оказалась неиспользуемой, если она используется в следующей строке?
В принципе, этой информации достаточно, чтобы, немного подумав, ответить на поставленный вопрос. Но если умные мысли в голову не приходят, и хочется ещё немного поприключаться, почему бы просто не спросить компилятор? На помощь приходит RTTI.
#include <iostream>
#include <typeinfo>
struct A {
A (int i) {}
};
struct B {
B (A a) {}
};
int main () {
int i = 1;
B b(A(i)); // (1)
std::cout << typeid(b).name() << std::endl;
return 0;
}
* This source code was highlighted with Source Code Highlighter.
При компиляции GCC 4.3 результатом выполнения этой программы является строка
F1B1AE
в которой зашифрована нужная нам информация о типе переменной (конечно, другой компилятор выдаст другую строку, формат вывода type_info::name() в стандарте не описан и оставлен на усмотрение разработчика). Узнать же, что означают эти буквы и цифры, нам поможет c++filt.
$ c++filt -t F1B1AE
B ()(A)
Вот и ответ: это функция, принимающая на вход параметр типа A и возвращающая значение типа B.
Причина
Осталось понять, почему наша строка проинтерпретировалась таким неожиданным способом. Всё дело в том, что в объявлении типа переменной лишние скобки вокруг имени игнорируются. Например, мы можем написать
int (v);
и это будет означать в точности тоже самое, что
int v;
Поэтому многострадальную строку (1) можно без изменения смысла переписать, убрав лишнюю пару скобок:
B b(A i);
Теперь невооружённым взглядом видно, что b это объявление функции с одним аргументом типа A, которая возвращает значение типа B.
Заодно мы объяснили странный ворнинг о неиспользованной переменной i — действительно, она не имеет никакого отношения к формальному параметру i.
Workarounds
Нам осталось только объяснить компилятору, что же на самом деле мы от него хотим — то есть, получить переменную типа B, проинициализированную переменной типа A. Самый простой способ — добавить лишних скобок, вот так:
B b((A(i)));
или так:
B b((A)(i));
Этого достаточно, чтобы убедить парсер, что это не объявление функции.
Как альтернативу, можно использовать форму вызова конструктора с помощью присваивания, если только конструктор не объявлен explicit:
B b = A(i);
Несмотря на наличие знака '=', никакого лишнего копирования здесь не происходит, в чём можно легко убедиться, заведя в классе B приватный конструктор копирования.
А можно просто ввести дополнительную переменную:
A a(i);
B b(a);
Правда, при этом потребуется лишнее копирование переменной a, но во многих случаях это приемлемо.
Выберите тот способ, который кажется вам более понятным :)
Навеяно постом в StackOverflow
P.S. Спасибо sse за уточнения.