This article aims to address naming conflicts and readability to the common macro by introducing an alternative. A dispatch macro, that serves as a portability wrapper, to be known as a characteristic.
Introduction
Macros are based on the #define
directive, which specifies a macro, comprising an identifier name and a string
or numerics, to be substituted by the preprocessor for each occurrence of that macro in the source code. Macros are commonly used for abstracting pragmas, declspecs, attributes, prerequisites, and feature checks.
Many programmers believe know that macros are evil. One of the major reasons for this is that they pollute the code base as they take over anything of the same name. That having been said, they are often, the only way to access compiler specific features in a cross platform manner. Macros have a delicate line to walk between two things. The first being unique naming. This goes to the code base pollution as macros are global. They are normally defined just before first use, and undefined directly after last use. This helps to offset the pollution. The second concern is making the name meaningful. The combination of the two leads to results that are often less than ideal. As a substitute, we will offer a portability wrapper, using attribute-style naming, that aims to solve this conflict.
Background
While working on a code base challenge introduced by a video on CppCon, I thought of a few alternatives to some of the more controversial subjects in programming. These ideas, that began purely academic, seemed as though they would have a wide array of actual uses. Although some of these alternatives required library structures to back them, which also would need explaining. An idea began to form. A series of articles that aims to tackle controversial concepts in coding and provide alternative means and methods. The first of which will be the most basic. The macro.
The Design
The basic design of what I am calling a characteristic is a macro that is fundamentally four separate macros acting in unison. The first of which is the dispatch id. The primary function of the id is to have a long and unique name from which conflicts are unlikely or improbable. This is also the location at which you build the macros functionality. Let's look at an example of what a possible id might look like.
#ifndef CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline
# if defined(__clang__)
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline \
__attribute__((always_inline, flatten, hot)) inline
# elif defined(__GNUC__) || defined(__GNUG__)
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline \
__attribute__((always_inline, flatten, optimize("-O3"))) inline
# elif (defined(_MSC_VER) && defined(_MSVC_LANG))
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline \
__pragma(auto_inline(on))__pragma(inline_recursion(on)) __forceinline
# else
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline inline
# endif
#endif // CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline
This macro defines a combination of attributes, pragmas, and keywords to comprise a forced inlining macro. This macro will target Msvc, Gcc, and Clang. It works stunningly on the given compilers, although it is the belief of this author that the keyword explicit should be used as a modifier for inline, amongst other things, to achieve this functionality in language proper. But I digress, all of the macro is upper case naming, with the exception of the trailing name. Take note of it as there will be more on this later.
The next macro is the forwarding macro. Its job is just what the name implies. It forwards the given token. In this macro, the given token will be concatenated with the prefix CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_
which will expand to our corresponding id.
# define CHARACTERISTIC_FORWARD_TO_ATTRIBUTE_DISPATCHER__( __DISPATCH_ID__, ... ) \
CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_ ## \__DISPATCH_ID__
__VA_ARGS__ __DISPATCH_ARGS__EMPTY__
The next macro is the dispatch macro itself. Which calls the forwarding macro. This call through adds the second set of parentheses completing the call to the forwarder. This gives us the attribute style syntax while ensuring the desired expansion on a variety of compilers.
# define CHARACTERISTIC_DISPATCHER( ... ) \
CHARACTERISTIC_FORWARD_TO_ATTRIBUTE_DISPATCHER__ __VA_ARGS__ __DISPATCH_ARGS__EMPTY__
The final macro is the access macro which exists for the sole purpose of undefining. As the id names are long, they are unlikely to ever cause naming conflicts. The access macro in contrast should be short and reflect the name of the project or library for which it is used. This access macro can be undefined and redefined on an as needed basis. Note that this is for demonstration purposes, as a more suitable, and shorter, library name should be used.
#ifndef __characteristic__
# define __characteristic__( ... ) CHARACTERISTIC_DISPATCHER( __VA_ARGS__ )
#endif // __characteristic__
Now we have a macro that we can define and undefine easily in our code, while the longer names persist. For added measure, here is an id that checks for the availability of constexpr
, and if it is found does a force inline constexpr
, else falls back to the forced inlining scheme alone.
#ifndef CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_constexpr
#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_constexpr
# CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline constexpr
#else
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_constexpr
# CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline
#endif
#endif // CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_constexpr
The dispatch macros can be condensed down to one macro. The identifiers are the last name after the last underscore. These are to be lowercase, as macros used as identifiers will expand before the full dispatch id is concatenated. This may be the desired effect. But most often is not, so using lowercase id endings is recommended so that an unwanted macro doesn't expand in place of the characteristic identifier. For this reason, the attribute syntax was chosen.
Using the Code
Although this is a contrived example, that could probably be done much simpler, the core idea is that the characteristic expands to a longer more unique macro that won't pollute the code base. This helps to keep the code legible without fear the name will reoccur. While the shorter, more legible name can be undefined. A more suitable usage would be decorating the structures and functions of std::invoke
for a zero overhead invoke. That just leaves the function call in its place. That being said, here is a simple factorial with our aggressive optimization.
#ifndef CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline
# if defined(__clang__)
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline
# __attribute__((always_inline, flatten, hot)) inline
# elif defined(__GNUC__) || defined(__GNUG__)
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline
# __attribute__((always_inline, flatten, optimize("-O3"))) inline
# elif (defined(_MSC_VER) && defined(_MSVC_LANG))
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline
# __pragma(auto_inline(on))__pragma(inline_recursion(on)) __forceinline
# else
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline inline
# endif
#endif // CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline
#ifndef CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_constexpr
#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_constexpr
# CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline constexpr
#else
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_constexpr
# CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_inline
#endif
#endif // CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_constexpr
#ifndef __characteristic__
# define CHARACTERISTIC_FORWARD_TO_ATTRIBUTE_DISPATCHER__( __DISPATCH_ID__, ... )
# CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_ ##
# __DISPATCH_ID__ __VA_ARGS__ __DISPATCH_ARGS__EMPTY__
# define __DISPATCH_ARGS__EMPTY__
# define CHARACTERISTIC_DISPATCHER_FUNCTION_MACRO( ... )
# CHARACTERISTIC_FORWARD_TO_ATTRIBUTE_DISPATCHER__
# __VA_ARGS__ __DISPATCH_ARGS__EMPTY__
# define __characteristic__( ... ) CHARACTERISTIC_DISPATCHER_FUNCTION_MACRO( __VA_ARGS__ )
#endif // __characteristic__
#include <type_traits>
#include <iostream>
#include <stdexcept>
__characteristic__((constexpr)) int factorial(int n)
{
return n <= 1 ? 1 : (n * factorial(n - 1));
}
template<int n>
struct ConstNOut
{
__characteristic__((inline)) ConstNOut() { std::cout << n << '\n'; }
};
int main()
{
std::cout << "4! = ";
ConstNOut<factorial(4)> out1;
volatile int k = 8; std::cout << k << "! = " << factorial(k) << '\n'; }
#undef __characteristic__
Even though these are reserved keywords, they expand to our corresponding id. So they overwrite nothing while having meaningful names. Here, a wrapped inline means forced inline. A wrapped constexpr
means force inline constexpr
. Where either unwrapped would be without the forced inlining. Macros for noinline, nodiscard, and other common ids are trivial to build. As an added bonus, the keywords have additional syntax highlighting as do attribute specifiers that have corresponding type names. Again keywords, class, and variable names are preferred as they are unlikely to be affected by other macros. This is the reason lowercase lettering is used on the trailing section of the identifier, as lowercase is less likely to be another macro. But these don't have to be limited to single identifiers. They can support function style macros with parameters. Such as...
#ifndef CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_error
# if defined(_MSC_VER) && !defined( __GNUC__ ) &&
# !defined( __GNUG__ ) && !defined( __clang__ )
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_error( MESSAGE )
# __pragma(message(": error: " \
MESSAGE))
# else # define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_error( MESSAGE )
# _Pragma( CHARACTERISTIC_STRINGIZE
# ( GCC error(CHARACTERISTIC_EXPAND__(MESSAGE)) ) )
# endif #endif // CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_error
#ifndef CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_warning
# if defined(_MSC_VER) && !defined( __GNUC__ ) &&
# !defined( __GNUG__ ) && !defined( __clang__ )
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_warning
# ( MESSAGE ) __pragma(message(": warning: " \
MESSAGE))
# else # define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_warning( MESSAGE )
# _Pragma( CHARACTERISTIC_STRINGIZE
# ( GCC warning(CHARACTERISTIC_EXPAND__(MESSAGE)) ) )
# endif #endif // CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_message
And the stringize it depends on...
#ifndef CHARACTERISTIC_STRINGIZE
# define CHARACTERISTIC_EXPAND_UNUSED__(Token) Token
# define CHARACTERISTIC_EXPAND__(x) CHARACTERISTIC_EXPAND_UNUSED__(x)
# define CHARACTERISTIC_STRINGIZE_UNUSED__(String) # String
# define CHARACTERISTIC_STRINGIZE(x) CHARACTERISTIC_STRINGIZE_UNUSED__(x)
# define CHARACTERISTIC_MACRO_ATTRIBUTE_DISPATCH_stringize
# ( RefString ) CHARACTERISTIC_STRINGIZE( RefString )
#endif // CHARACTERISTIC_STRINGIZE
The usage...
#if !defined(__cplusplus)
__characteristic__((error("C++ compiler required.")))
#elif !defined(_MSC_VER) && !defined( __GNUC__ ) &&
#!defined( __GNUG__ ) && !defined( __clang__ )
__characteristic__((warning("Unknown compiler details.")))
#endif
Points of Interest
Feel free to try the sample on Compiler Explorer. If you do, the __pragma
statements will have to be removed from the Msvc version of inline, as CE balks at them. The actual compiler does accept them and they are needed to unconditionally turn on inlining as the __forceinline
keyword only works if the "only inline", or "any suitable", compiler switches are chosen. A direct copy and paste requires you to clean up the line endings that this site uses. Originally, this was designed to be used with just with attributes. But it came in handy so often, with other uses, that I thought to share. Hearing feedback on breaking the naming convention is one of the main goals of this exercise.
For an actual use case see Wild West Coding: E.B.C.O. Compression. The characteristics header will be updated along with the articles they are associated with. The library uses the access point JOE
, as opposed to __characteristic__
, as it reflects the name of the library to witch it is contained.
History
- 22nd August, 2021: Initial version