Summary
We need a policy on phasing out C++03 support in Boost.
C++03 support is holding us back. It impedes development, increases maintenance costs, increases dependencies, and increases compilation times.
Library authors and maintainers need to have an approved mechanism for dropping C++03 support in their libraries.
The suggested way forward is to allow library authors to declare C++03 support deprecated via a notice in the documentation and a message issued at compile time, then be allowed to drop C++03 support no earlier than two Boost releases later.
For instance, a library may declare C++03 support deprecated in Boost 1.73, and drop it no earlier than in Boost 1.75.
Frequently Asked Questions
"I thought library authors were free to only support C++17?"
This is Boost policy for new library submissions. It’s obviously unworkable for old and established libraries (that are currently in maintenance mode) on which many other libraries depend, both in Boost and outside of it.
For example, Boost is a dependency of hundreds, if not thousands, Debian packages. We have a responsibility to not break them unnecessarily.
"Why not use C++11 only if available?"
In addition to increasing maintenance costs, this also creates other
problems. Let’s consider the scenario in which a Boost library uses,
for example, std::function
under C++11 and boost::function
under
C++03.
namespace lib1
{
#if CXX11
using std::function;
#else
using boost::function;
#endif
struct lib1_type
{
function<void()> f_;
};
} // lib1
It’s common, at least under Linux, for user code to use a system-provided Boost (which is often compiled under C++03), and to use f.ex. C++14 or even C++17 itself.
This means that lib1_type
will have different definitions in the precompiled
Boost library and in user code, which leads to hard to track undefined behavior.
To avoid these problems, lib1
should always use std::function
.
"What does C++11 mean anyway?"
Every library author will be allowed to determine which C++11 features are
required. Some libraries may only need std::function
and std::tuple
; others
may require defaulted functions.
In general, though, a reasonable minimum water mark will be variadic templates and
rvalue references (including std::move
and std::forward
.)
To take two concrete examples, GCC before 4.8 and Microsoft Visual C++ before
2013 cannot really be considered C++11 compilers. (Again, a library will be free
to consider f.ex. VC++ 2012 or GCC 4.6 with -std=c++0x
adequate if it so wishes.)
Ongoing Costs of Maintaining C++03 Support
Impeded Development
If a library supports C++03, contributions that use C++11 features have to either be rejected, or have C++03 workarounds applied to them.
Since C++11 features such as variadic templates and perfect forwarding significantly increase productivity, this slows down the pace of development.
Increased Maintenance Costs
Even libraries in maintenance mode need to be updated to support newer standards, and C++03 code is significantly costlier to change due to, f.ex. use of the preprocessor to emulate variadic templates and use of numerous overloads to emulate perfect forwarding.
In addition, C++03 compilers are generally older and have more quirks and issues that need to be worked around.
Increased Dependencies
Libraries that support C++03 have to use Boost components even when a corresponding
standard library component exists in C++11. Examples are std::function
, std::bind
,
std::tuple
, std::shared_ptr
, <type_traits>
, std::fpclassify
, and so on.
Increased Compilation Times
One common complaint against Boost is high compilation times. Nearly all such objections can be traced to use of either the preprocessor library or Boost.MPL, and the reason is that without variadic templates, a high number of overloads or specializations need to be generated and parsed.
If we require C++11, it would be possible, at least in principle, to gradually switch all such uses into the corresponding C++11 construct, whether by using variadic templates directly, move from use of Boost.MPL to use of Boost.Mp11, or even by rewriting the internals of Boost.MPL in a more modern style.
Suggested Policy
A library author or maintainer will be allowed to announce that C++03 support in the library is deprecated.
This announcement will take the following forms:
-
A note in the documentation;
-
An item in the release notes in the Boost release deprecating C++03 support;
-
A message issued at compilation time if a library header is included in C++03 mode.
The recommended form of the code issuing the message will be
#include <boost/config.hpp>
#include <boost/config/pragma_message.hpp>
#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_NO_CXX11_RVALUE_REFERENCES) || defined(BOOST_NO_CXX11_HDR_MEMORY)
BOOST_PRAGMA_MESSAGE("C++03 support is deprecated in Boost.Library 1.73 and will be removed in Boost.Library 1.76.")
#endif
with the condition of the #if
directive adjusted appropriately to reflect the actual
library requirements.
At least two Boost releases must ship with a deprecation notice before support is dropped.
Costs of Dropping C++03 Support
C++03 Users will be Unable to Upgrade Boost
The most obvious cost of a Boost library no longer supporting C++03 is that users of this library who are still under C++03 will be unable to upgrade Boost beyond the release that drops C++03 support.
Assuming that this policy is adopted today, the earliest such occurence will be in December 2020. C++11 will be almost ten years old then, a reasonably long availability period for even the most conservative projects.
Building Boost May Fail
The default C++ standard in GCC 5 and earlier, and Clang 5 and earlier, is 03, which implies
that building Boost via the default b2 install
invocation will fail on these compilers.
However, the system compiler on the Linux distributions in use in 2021 is expected to be GCC 6
or later. In addition, building b2
itself requires C++11 already, which rules out VC++
versions earlier than 2013.
Switching from boost::
to std::
Components May Change Behavior
For Boost components that have direct C++11 standard library equivalents, such as boost::bind
,
it might seem natural to consider replacing their Boost definitions with a using directive, as in
// boost/bind.hpp
#include <functional>
namespace boost
{
using std::bind;
}
But it’s not so simple.
Boost components often differ from the corresponding standard component in subtle ways. For example,
applying std::ref
to std::reference_wrapper<X>
yields std::reference_wrapper<X>
, but the
equivalent Boost code yields boost::reference_wrapper<boost::reference_wrapper<X>>
. Replacing
boost::ref
with a using declaration for std::ref
will break code relying on the Boost behavior,
such as Boost.Proto.
Replacing boost::bind
with a using declaration for std::bind
as in the above code causes
different problems. First, boost::bind
supports relational operators as an extension; second, it
can perform limited overload resolution when applied to an overloaded function; third, it recognizes
boost::reference_wrapper
, and std::bind
doesn’t, which requires boost::ref
to be migrated to
std::ref
first; fourth, specializations of boost::is_placeholder
will break; and so on.
Replacing boost::function
with a using declaration for std::function
will, in addition to
breaking all code relying on boost::function
extensions such as allocator support, contains
,
and operator==
, also change the behavior of target
when the function
stores a reference_wrapper
.
It turns out that after std::function<void()> f2 = std::ref(f);
, f2.target<F>()
does not return
&f
as it does for boost::function
.
Is it Still Worth Doing?
Yes. Enough is enough.