Poash Technologies
Poash Technologies

Feature Testing Macros in C++20

01.01.25 11:42 AM Comment(s) By Umar Lone

Feature Testing Macros in C++20: Ensuring Feature Compatibility and Consistency

The C++20 standard introduced a plethora of new features and enhancements, further solidifying C++'s reputation as a powerful and versatile programming language. Among these additions, a particularly noteworthy set of features is the preprocessor macros designed to check if specific C++ features are implemented by the compiler. These macros provide a simple and portable way to ensure feature compatibility across different compilers and versions.


Problems & challenges

Newer C++ standards have brought in lot of cutting-edge features that many programmers would want to use. These feature go in the working draft of the standard and then approved after changes or fixes. In some cases, the feature is already implemented by a compiler before it is approved by the standard. How do the developers utilize these features? Some may rely on the value of __cplusplus macro to check for the conformance to a specific standard. However, it is not fine grained; it is impossible to check for support for a specific language feature by the compiler.

__cplusplus in Visual Studio

MSVC compiler still uses the old value for this macro i.e., 199711L, even after compiling with a newer standard. This is due to backward compatibility with old code that fails to work if the value of this macro changes.

To get the correct value according to the C++ standard version that the compiler is using, you have to the this switch during compilation.

/Zc:__cplusplus

As you can see, this macro is not reliable across compilers to check for conformance to a specific standard

Therefore, to check for conformance, the developers have to rely on non-standard & compiler-specific preprocessor directives.

E.g., in MSVC compiler, you might use the following code to conditionally compile based on the Visual Studio version.

#if (_MSC_VER == 1500)

  //Visual Studio 2008 specific stuff

#elif (_MSC_VER == 1600)

  //Visual Studio 2010 specific stuff

#elif (_MSC_VER == 1700)

  //Visual Studio 2012 specific stuff

#endif

This has two disadvantages:

1.  The macro is specific to MSVC compiler and cannot be used in other C++ compilers

2.  You’ve to know the exact compiler version that supports the corresponding feature you want to use

Let me explain the second point. Let’s assume we want to use C++11 noexcept specifier. It was added in Visual Studio 2015. So, to check for its existence, we’ve to write the following code:

#if _MSC_VER >=1900

#define CONSTEXPR constexpr

#else

/*

 * If constexpr is not available, 

 * use inline to avoid ODR violations

 */

#define CONSTEXPR inline

#endif

 

CONSTEXPR int Square(int x) {

  return x * x ;

}

Here’s another example that uses empty throw specification if the compiler does not support noexcept.

#if _MSC_VER >=1900

#define NOEXCEPT noexcept

#else

#define NOEXCEPT throw()

#endif

 

#include #include int main() { int x = 5 ; std::cout << std::format("__cpp_ranged_based_for :{}\n", __cpp_range_based_for) ; std::cout<< std::format("__cpp_raw_strings :{}\n", __cpp_raw_strings) ; std::cout<< std::format("__cpp_structured_bindings :{}\n", __cpp_structured_bindings) ; std::cout<< std::format("__cpp_lib_any :{}\n", __cpp_lib_any) ; std::cout<< std::format("__cpp_lib_chrono :{}\n", __cpp_lib_chrono) ; std::cout<< std::format("__cpp_impl_coroutine :{}\n", __cpp_impl_coroutine) ; }
#include <iostream>
#include <format>
int main() {
    int x = 5 ;
 
    std::cout << std::format("__cpp_ranged_based_for     :{}\n", __cpp_range_based_for) ;
    std::cout << std::format("__cpp_raw_strings          :{}\n", __cpp_raw_strings) ;
    std::cout << std::format("__cpp_structured_bindings  :{}\n", __cpp_structured_bindings) ;
    std::cout << std::format("__cpp_lib_any              :{}\n", __cpp_lib_any) ;
    std::cout << std::format("__cpp_lib_chrono           :{}\n", __cpp_lib_chrono) ;
    std::cout << std::format("__cpp_impl_coroutine       :{}\n", __cpp_impl_coroutine) ;
}
#include <iostream>
#include <format>
int main() {
    int x = 5 ;
 
    std::cout << std::format("__cpp_ranged_based_for     :{}\n", __cpp_range_based_for) ;
    std::cout << std::format("__cpp_raw_strings          :{}\n", __cpp_raw_strings) ;
    std::cout << std::format("__cpp_structured_bindings  :{}\n", __cpp_structured_bindings) ;
    std::cout << std::format("__cpp_lib_any              :{}\n", __cpp_lib_any) ;
    std::cout << std::format("__cpp_lib_chrono           :{}\n", __cpp_lib_chrono) ;
    std::cout << std::format("__cpp_impl_coroutine       :{}\n", __cpp_impl_coroutine) ;
}