Компилятор GNU G++ 5.1.0 не компилирует следующий код:
#include <vector>
using namespace std;
vector <pair <int, int>> a(0);
int main()
{
return 0;
}
Однако успешно компилирует следующий код:
#include <vector>
using namespace std;
#define pii pair <int, int>
vector <pii> a(0);
int main()
{
return 0;
}
Почему 2 код компилируется, хотя после "pii" нет пробела? Особая обработка defin'ов?
Думаю, да, особая обработка define'ов. Проблема с
>>
идёт (шла?) на уровне разбора файла на лексемы:>>
в зависимости от контекста должно быть либо двумя лексемами (в случае шаблонов), либо одной (побитовый сдвиг). Из-за этого непонятно. А вот когда есть#define
, то всё понятно, потому что лексемы#define
так просто не скеливает.Кстати, в C++ вместо
#define
для типов принято использоватьtypedef
. Использовать так: представляем, что нам нужно объявить переменную, объявляем, а имя переменной заменяем на новое имя типа, перед объявлением дописываемtypedef
. Например:Смысл: сложнее выстрелить себе в ногу. Например, если вы сделаете
#define PCHAR char*
, то конструкцияPCHAR a, b
развернётся макросами вchar* a, b
, что на самом деле значитchar *a, b
, т.е.a
будет указателем, аb
— просто char. Похожие проблемы (уже не в олимпиадах) возникают сconst
, и другими сложными типами (вроде указателя на функцию).Спасибо за ответ. Но "оборачивание" целых функций в define нежелательно? Функции стоит оформлять "стандартно"?
Тоже верно.
#define
вообще стоит использовать по минимуму. Классический пример — функцияmin
:А теперь представим, что у нас есть функция
go
, которая что-нибудь перебирает:И вот тут мы получим, что функция была вызвана дважды, а не трижды. Аналогичный пример:
И вот тут мы в одном из случаев получаем не просто возможность увеличения
a
на двойку, а хуже — неопределённое поведение, так как переменная не имеет права меняться дважды в одной операции (привет от++i + ++i
, это даже на лурке есть).I tried this on ideone:
It doesn't compile with C++ 5.1 but does with C++14. Interesting, please someone explain this, too :D Is it some sort of smart compiler optimization?
This is off the top of my head, so the details may not be 100% precise.
Older versions of C++ standard (standard itself, not compilers) don't allow writing >> without a space when nesting templates because it makes parsing harder (">>" looks just like the operator ">>"). The compiler may compile such code (as is the case with any undefined behavior), but it doesn't have to.
Newer versions of C++ standard (IIRC, already C++11?) allow omitting the space.
Here is how programming language parsing often works. Some programs may do it differently or have some exceptions. C++ is extra nasty in this sense and blurs the border between the steps, but general idea remains.
Compiler/interpreter frontends have 2 stages:
Instead of working with sequences of characters directly parsers often work with sequences of tokens. Each token is sequence of one or more symbols. One token could be math operator
+
*=
, identifiermain
i
myFunction
, constant3
"abc"
True
, special keywordif
else
. This has multiple benefits two simpler languages instead of one complicated, more easily ignore whitespaces, alternative character sequences for the same token (and
==&&
), hacks like handling python indentation in smart lexer.Lexers are usually simple and don't know much about the whole program instead they try to greedily create one of the tokens from not knowing if it form a correct program or not.
In this case we are interested in these tokens (real C++ is more complicated than that, but it's good enough for explaining):
[a-zA-Z]+
>>
<
>
Templates in <=C++03 expected something like
IDENTIFIER LT argument_list GT
.R_SHIFT
is clearly not the same asGT
so it causes parsing error. C++11 changed the rules to allow >> for ending template, but it makes formal language description even more complicated.As for why second version of code doesn't fail. In c++ preprocessor works with sequences of tokens not sequences of characters and after being recognized as two separate tokens they aren't combined unless special token concatenation operator ## is used.
It means that character sequence
vector<pii>
first gets split into tokensidentifier(vector)
<
identifier(pii)
>
. In the next step preprocessor recognizes that identifier pii is macro and replaces it with matching sequence of tokensidentifier(vector)
<
int
>
. Parser doesn't know if two>
tokens came from two space separated '>' character or macro expansion so they are treated the same.Here are some examples which are not affected by c++11 changes in template parsing.