Jens Gustedt's Blog

December 18, 2013

right angle brackets: shifting semantics

Filed under: C++, rants — Jens Gustedt @ 23:29

As I showed int this post, using > as right angle brackets was not a particularly good idea, but trying to patch this misdesign even makes it worth. After a bit of experimenting I found an expression that is in fact valid for both, C++98 and C++11, but that has a different interpretation in both languages:

fon< fun< 1 >>::three >::two >::one

So if you have to maintain a large code base with templates that depend on integers that are perhaps produced automatically by some tools, be happy, you will not be out of work for a while: changing your compiler to C++11 might change the semantics of your code.

Let’s have a look how this expression is parsed

fon< fun< 1 >>::three >::two >::one    // in C++98
          -----------
     -----------------------
-----------------------------------
fon< fun< 1 >>::three >::two >::one    // in C++11
     --------
---------------------
----------------------------
-----------------------------------

C++98

So for C++98 the innermost level has

1 >> ::three

the shift operator applied to 1 with an operand of a global identifier ::three, say X. This X is now template argument to fun

fun< X >::two

the member two of template class fun, lets call this Y. The last level is then

fon< Y >::one

The member one of template class fon.

C++11

fun< 1 >

is the template class fun for parameter 1, say fun1.

fon< fun1 >::three

a member three of the fon class, say Z.

Z > ::two

Z compared to the global symbol two, resulting in a bool W.

W > ::one

W compared to a global symbol one.

An instantiation of that mess

Now that we have seen that in both languages that expression can be parsed, we want to have an instantiation that is correct in both, but where the semantic differs. In fact the following is a correct program, that allows to distinguish C++98 and C++11 compilers.

#include <stdio.h>

bool const one = true;
int const two = 2;
int const three = 3;

template< int > struct fun {
  typedef int two;
};

template< class T > struct fon {
  static int const three = ::three;
  static bool const one = ::one;
};

bool const cpp98 = fon< fun< 1 >>::three >::two >::one;
//bool const as_cpp98 = fon< fun< (1 >>::three) >::two >::one; // valid for both
//bool const as_cpp11 = (fon< fun< 1 >>::three) >::two >::one; // not valid in C++98

int main(void) {
  printf("this is a %sC++11 compiler, version is %ld\n", (cpp98 ? "pre-" : ""), __cplusplus);
}

In the interpretation of both, the expression in question is of type bool, but for different reasons. For C++98 it is because the expression is the class member one of fon. This is always a bool with value true.

For C++11, it is the result of a comparison between two bool values, so bool, too. The value is false, here.

Conclusion

Not only is C++ the victim of some poor choices that have been made at the start. The committee doesn’t succeed in avoiding such mistakes for new versions of the standard. Maintaining a C++ code base where the language shifts under your feet was and still is a nightmare. In my limited experience, before it was a question of syntax changes that errored out under compilation. Now we have a state where valid code for one version remains valid for the other, but the result is different.

That language is definitively not for me.

Advertisements

Blog at WordPress.com.