Boost logo

Boost :

From: Gavin Lambert (boost_at_[hidden])
Date: 2020-12-01 23:39:59


On 2/12/2020 11:01 am, Edward Diener wrote:
> On 12/1/2020 4:36 PM, Kostas Savvidis wrote:
>> #ifndef BOOST_NO_CXX11_HDR_ARRAY
>> #include <array>
>> #define mixmaxarray std::array
>> #else
>> #include <boost/array.hpp>
>> #define mixmaxarray boost::array
>> #endif
>> ...#undef mixmaxarray
>>
>> The other alternative kindly proposed by Steven was:
>> namespace boost { namespace random { namespace arrayns = std; } }
>
> I wrote a library called cxx_dual ( https://github.com/eldiener/cxx_dual
> ) to solve a problem like yours, but it did not meet with general
> approval. With cxx_dual your code above could have been:
>
> #include <boost/cxx_dual/array.hpp>
> #define mixmaxarray cxxd_array_ns::array

You shouldn't use #define for that sort of thing anyway. Define a local
typedef:

namespace boost
{
     namespace mylib
     {
         typedef cxxd_array_ns::array mixmaxarray;
     }
}

(Or a "using" alias, if you can assume C++11.)

However this is only actually correct behaviour if it's a header-only
library.

In a compiled library, what you actually should do is something closer
to this:

// header file
#if SOME_CONDITION
typedef std::foo myfoo;
namespace detail1 { class aclass { ...; std::foo m_foo; }; }
typedef detail1::aclass aclass;
#else
typedef boost::foo myfoo;
namespace detail2 { class aclass { ...; boost::foo m_foo; }; }
typedef detail2::aclass aclass;
#endif
class commonclass { void method(myfoo const& x); };

// cpp file 1
#if CAN_COMPILE_STD_FOO
// or equivalently set build rules to skip this file if std::foo doesn't
// exist at build time
void commonclass::method(std::foo const& x) { ... }
// implementation for detail1::aclass
#endif

// cpp file 2
void commonclass::method(boost::foo const& x) { ... }
// implementation for detail2::aclass

Note that the condition used to select the implementation in the header
file is not necessarily the same as the one used to decide whether to
build the std implementation (the first may be influenced by a user
preference, the second is not). Also note that the two implementations
are in separate cpp files because linkers have an easier time throwing
away entire object files if they're not referenced (at least in static
libraries), and may be less good at doing that for individual symbols.
And you *don't* make building the second file conditional.

If these rules are followed, and as long as the library is compiled with
the highest-desired-C++-standard, this allows apps to link against it
using either class safely -- you can even have a mix of both in the same
app provided those components don't try to directly talk to each other.

Actually doing these things in any non-trivial library, however, is
*very hard* -- not impossible, if you leverage some preprocessor tricks
-- but it's not surprising that most people don't want to do it until
they have no other choice. There's also a lot of subtle complications
where the compiler doesn't especially help you to catch violations, for
things even as simple as private members; pimpl helps but it's still
tricky, and has performance tradeoffs. ("aclass" avoids those problems
but introduces some of its own.)

And it quickly snowballs (as you add more dependencies) into an
unmanageable mess, unless you can take a whole slate of dependencies as
a fixed unit rather than having them individually configurable.

(It's perhaps worth noting that this doesn't *only* apply to compiled
libraries; this is similar to how glibc handles the different
implementations of std::string between C++03 and C++11, for example.)

C++ ABI is *hard*.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk