Component of the Week #13: bsls_compilerfeatures
- Summary:
Provide a set of preprocessor macros to detect the current compiler, C++ version, and its supported features.
Since C++11 was released, the C++ Standards Committee (WG21) has been reliably following a train-based model for releasing new Standards — every three years, all features that are ready to ship get on the train and ship as part of a new Standard. Each compiler supported by BDE then has its own release cycle where features are implemented incrementally with varying levels of quality. On top of that, each compiler allows users to select the C++ Standard to compile with.
The many different possibilities for which C++ features are
actually reasonable to use lead to a maze of possibilities to navigate.
In C++20, the C++ Standard officially introduced official feature
test macros — such as __cpp_concepts — to identify whether particular
compiler features are available and to what extent they have been
implemented. These macros, however, were for a long time before that
not standard, and they don’t take into account when a compiler says it does
support a feature but it is not yet actually stable enough to use
reliably in production.
To navigate these complexities, BDE provides a single component with macros that reliably expose the language features that are available — bsls_compilerfeatures. This component is typically most valuable when used to construct higher-level facilities that can be used portably, e.g. the macros in bsls_keyword, bslma_movableref, or any of the components in the bsla package. When considering the use of bsls_compilerfeatures (either directly or indirectly) it is worth understanding the range of languages variants the software is actually intended to support, and the trade off in terms of maintainability that comes with the use of conditional compilation.
Consider, for example, a simple class written long ago that does nothing special in its default constructor:
class PickleHandle
{
// ...
public:
PickleHandle();
// ...
private:
bsl::shared_ptr<Pickle> d_pickle;
};
PickleHandle::PickleHandle()
: d_pickle()
{}
Now we realize that we don’t intend to do more in this constructor,
but we need to keep declaring it because of the other
constructors on PickleHandle. In C++11, we can reduce
our maintenance burden by defaulting this constructor, so we
can evolve our code to take advantage of that when possible:
class PickleHandle
{
// ...
public:
PickleHandle()
#ifdef BSLS_COMPILERFEATURES_SUPPORT_DEFAULTED_FUNCTIONS
= default;
#else
;
#endif
// ...
private:
bsl::shared_ptr<Pickle> d_pickle;
};
#ifndef BSLS_COMPILERFEATURES_SUPPORT_DEFAULTED_FUNCTIONS
PickleHandle::PickleHandle()
: d_pickle()
{}
#endif
Of course, looking at this one might question the wisdom of our change, given that we’ve added significantly more lines of code to achieve no concrete benefit for us or our users. That might be true, but this is an interim step. At some point, our pickle-focused library will stop supporting platforms that do not support defaulted functions, and we can then rather mechanically remove a pile of clearly marked code by stating that requirement once at the top of our header, changing our source code to cleanly use modern C++ syntax and features:
#ifndef BSLS_COMPILERFEATURES_SUPPORT_DEFAULTED_FUNCTIONS
#error Pickle Lib requires defaulted functions
#endif
class PickleHandle
{
// ...
public:
PickleHandle() = default;
// ...
private:
bsl::shared_ptr<Pickle> d_pickle;
};
This same approach can be used for a wide variety of other
modern C++ features, such as concepts, the spaceship operator,
various attributes, and many aspects of compile-time programming
with constexpr.
- If you’d like to know more, check out:
The documentation for bsls_compilerfeatures.
The components bsls_platform and bsls_libraryfeatures that expose other aspects of the build-time environment.
Components such as bsls_keyword and those in the bsla package that build higher-level abstractions on top of bsls_compilerfeatures.