Today I want to describe an issue I bumped onto recently. I had a function template that deals with two parameters — T and U and needed to make sure that one is convertible to the other. In C++11 I would write:

template <typename T, typename U> T fun(T v, U u) { static_assert(std::is_convertible<U, T>::value, ""); // the code }

But since I need my code that also needs to work on older compilers, I decided to go with Boost.StaticAssert. It is one of Boost libraries that allows the migration from old to new compilers. I just write:

template <typename T, typename U> T fun(T v, U u) { BOOST_STATIC_ASSERT(boost::is_convertible<U, T>::value); // the code }

On newer compilers this is replaced with keyword static_assert ; on older ones, it uses some sophisticated C++03 tricks to achieve (almost) the same result:

#if CXX11_COMPILER #define BOOST_STATIC_ASSERT(expr) static_assert((expr), "") #else #define BOOST_STATIC_ASSERT(expr) SOME_TRICKS(expr) #endif

I tested it on two compilers and it worked fine, so I committed the code, but when the full regression tests were run on many more compilers, some of them rejected my code as invalid. Do you know what they said? “Attempted to pass two arguments to a macro that expects only one argument.” If you look at the code above and if you are familiar with how macros work, the error message is clear. What the preprocessor sees it this:

BOOST_STATIC_ASSERT(__some_characters__, __some_characters__);

That is, what we interpret as opening and closing angle brackets in a template, to a preprocessor is only a character. The preprocessor only assigns special meaning to commas and parentheses. Therefore, in order to fix the bug it is recommended that you use additional parentheses:

template <typename T, typename U> T fun(T v, U u) { BOOST_STATIC_ASSERT((boost::is_convertible<U, T>::value)); // the code }

Now, the preprocessor sees it differently:

BOOST_STATIC_ASSERT((__some_characters__)); // 1 argument // processed into // static_assert(((__some_characters__)), "");

You probably know that too. But what struck me was something different. Why is it only some compilers that rejected my code. Why did most of the other compilers accept my apparently buggy code and produced the right result?

The answer is in the title of this post. Boost.StaticAssert is clever and wherever possible it uses variadic macros. The macro is now defined something like this:

#if _COMPILER_HAS_VARIADIC_MACROS #if _COMPILER_HAS_STATIC_ASSERT #define BOOST_STATIC_ASSERT(...) static_assert((__VA_ARGS__), "") #elif #define BOOST_STATIC_ASSERT(...) SOME_TRICKS(__VA_ARGS__) #endif #endif //_COMPILER_HAS_VARIADIC_MACROS

Now, the following code:

template <typename T, typename U> T fun(T v, U u) { BOOST_STATIC_ASSERT(boost::is_convertible<U, T>::value); // the code }

is still recognized as two arguments passed to macro, but it is now OK: all these arguments along with the separating commas are forwarded further (e.g., to static_assert ) to the code that knows how to interpret these tokens. This is a very nice usage of variadic macros.

But you can also wonder if it is a good idea to use this trick in Boost.StaticAssert. If the trick wasn’t there I would have immediately spotted the problem in my program, in my compilers. But because the library tried best to work around my bug on my compilers, it concealed the problem until the program was tested in other environments. In order for my code to be portable I still need to use the additional parentheses anyway.