19

In C++:

  assert(  std::is_same<int , int>::value  ); // does not compile

  assert( (std::is_same<int , int>::value) ); // compiles

Can anyone explain why?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
igbgotiz
  • 892
  • 1
  • 8
  • 28
  • 4
    Macros disregard compiler semantics (those don't exist yet when macros are expanded). You can pass just about anything separated by commas. – chris Jul 17 '14 at 02:01
  • It's worth noting that these parentheses are what make the four [Boost PP data types](http://www.boost.org/doc/libs/1_55_0/libs/preprocessor/doc/index.html) possible. – chris Jul 17 '14 at 02:05
  • 3
    `std::is_same` is a compile-time check , so it does not make much sense to do a run-time assert anyway! Do a compile-time assert instead. In C++11 there is [static_assert](http://en.cppreference.com/w/cpp/language/static_assert) , otherwise there are still [a lot of options](http://stackoverflow.com/questions/174356/ways-to-assert-expressions-at-build-time-in-c) – M.M Jul 17 '14 at 02:05
  • @MattMcNabb that is an assumption you should not make as I believe I have the power to write anything that is within the specification of the c++ standard. maybe it is my intention to delay an assertion to run time despite it being determinable at compile time. maybe my company a check-in validation system that would only let you submit code that compiles and i just want to go home now, so I chose to use run-time assert to pass the check-in system. – igbgotiz Jul 17 '14 at 02:26
  • @ShafikYaghmour, I couldn't think of a good way to say it :p Your answer is probably a good way of saying it. – chris Jul 17 '14 at 03:15
  • 1
    @igbgotiz: In that case you are _knowingly and deliberately checking in broken code_. I'd fire you. (the broken code is not the assert nor the is_same, the broken code is whatever causes it to get triggered) – Mooing Duck Jul 17 '14 at 22:37

2 Answers2

14

assert is a preprocessor macro. Preprocessor macros are dumb; they don't understand templates. The preprocessor sees 10 tokens within the parentheses:

assert( std :: is_same < int , int > :: value );

It splits at the comma. It doesn't know that this is the wrong place to split at, because it doesn't understand that std::is_same<int and int>::value aren't valid C++ expressions.

The preprocessor is smart enough to not break up the contents of inner pairs of parentheses across multiple arguments. That's why adding the extra parentheses fixes the problem.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • 5
    Also a `static_assert` is enough here : `static_assert( std::is_same::value, "oops" );` – quantdev Jul 17 '14 at 02:05
  • It's not just templates. Neither braces nor brackets protect commas. Parentheses and quotes do. – rici Jul 17 '14 at 02:19
  • That does not quite make sense. The preprocessor obviously knows that "assert" expects exactly one argument, which is why it's able to report the error in the first place. So why would the preprocessor want to do any splitting at all? If assert was a macro that expected two or more arguments, I can easily understand an attempt at splitting. But assert, as both you, I and the preprosessor surely knows, takes only one argument. So it's beyond me why the preprocessor would bother with a split attempt at the comma and tell me I gave too many arguments. – igbgotiz Jul 17 '14 at 02:19
  • @igbgotiz If the preprocessor didn't split at the comma here, that would be a *special case*: ignore commas if there's only one argument expected, don't ignore them if there's more than one. I can't speak for the designers of C, but I think special cases like that are best avoided in general. – Brian Bi Jul 17 '14 at 02:33
  • @Brian Well, not "ignore" per se. Just that assume the programmer is not possibly stupid enough to supply more than one argument to a single-argument macro and let it go as far as it can until it can be definitely determined that a source error is in the code, which in my case would have turned out that the programmer was right all along if the macro preprocessor had had a little more faith in his intelligence. – igbgotiz Jul 17 '14 at 02:43
  • @igbgotiz Again, it would be a special case, would it not? That is, there would be different rules depending on the number of arguments. If one argument is expected, then skip ahead to the matching parenthesis. If more than one argument is expected, then watch for commas. Like I said, I think it's generally a bad idea to have special cases like this. – Brian Bi Jul 17 '14 at 02:51
  • @Brian Well, special cases are an inherent part of programming. If you use any of the functional languages, then special cases are essentially the building blocks of your programs. – igbgotiz Jul 17 '14 at 03:12
  • 1
    @igbgotiz It's not really the same. A *program* only does what you tell it to do. If you need to write a function with a base case and a recursive case, there's nothing wrong with that; it's clearly spelled out there in the code. A *programmer* should not have to consult the standard in the same way that a *program* always keeps going to the next line of code. Special cases in a programming language's semantics should be avoided, so that the programmer can expect language features to work in the most obvious way possible---even if it's stupid. – Brian Bi Jul 17 '14 at 04:28
11

The comma is being treated as a argument separator for the macro, but parenthesis in your second case protect the arguments. We can see this by going to the draft C++ standard section 16.3 Macro replacement which says (emphasis mine):

The sequence of preprocessing tokens bounded by the outside-most matching parentheses forms the list of arguments for the function-like macro. The individual arguments within the list are separated by comma preprocessing tokens, but comma preprocessing tokens between matching inner parentheses do not separate arguments. If there are sequences of preprocessing tokens within the list of arguments that would otherwise act as preprocessing directives,154 the behavior is undefined

We can see that macro expansion happens before semantic analysis by going to section 2.2 Phases of translation and see that phase 4 is includes:

Preprocessing directives are executed, macro invocations are expanded, and [...] All preprocessing directives are then deleted.

and phase 7 includes:

[...]Each preprocessing token is converted into a token. (2.7). The resulting tokens are syntactically and semantically analyzed and translated as a translation unit[...]

As a side note we can see the Boost includes a special macro to deal with this situation: BOOST_PP_COMMA:

The BOOST_PP_COMMA macro expands to a comma.

and says:

The preprocessor interprets commas as argument separators in macro invocations. Because of this, commas require special handling.

and an example:

BOOST_PP_IF(1, BOOST_PP_COMMA, BOOST_PP_EMPTY)() // expands to ,
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740