1074 | | As mentioned earlier, the libraries reviewed overwhelmingly chose to use STL11 over any equivalent Boost libraries, so `std::thread` instead of `boost::thread`, `std::shared_ptr` over `boost::shared_ptr` and so on. This makes sense right now as STL11 and Boost are still fairly close, however in the medium term there will be significant divergence between Boost and the STL as Boost "gets ahead" of the STL in terms of features. Indeed, one may find oneself needing to "swap in" Boost to test one's code with some future STL pattern not yet standardised. |
1075 | | |
1076 | | Let me put this another way: imagine a near future where Boost.Thread has been rewritten atop of the STL11 enhancing the STL11 threading facilities very substantially indeed with features which may not enter the standard until the 2020s. If your library is bound to only use the STL, you may lose out on substantial performance or feature improvements. Your users may clamour to be able to use Boost.Thread with your library. You will then have to duplicate an additional code path for Boost.Thread which replicates the STL11 threading path. But you still may not be done - what if Boost.Chrono also adds significant new features? Or Boost.Regex? Or any of the Boost libraries now standardised into the STL? |
1077 | | |
1078 | | Sometimes multiple code paths are unavoidable, specifically when you make use of Boost enhancements over the STL. However a large amount of the time one can treat the STL11 and the Boost implementation as being ''substitutable'' for one another, so now all you need is a method for your library's users to: |
| 1074 | As mentioned earlier, the libraries reviewed overwhelmingly chose to use STL11 over any equivalent Boost libraries, so hardcoded `std::thread` instead of `boost::thread`, hardcoded `std::shared_ptr` over `boost::shared_ptr` and so on. This makes sense right now as STL11 and Boost are still fairly close in functionality, however in the medium term there will be significant divergence between Boost and the STL as Boost "gets ahead" of the STL in terms of features. Indeed, one may find oneself needing to "swap in" Boost to test one's code with some future STL pattern shortly to become standardised. |
| 1075 | |
| 1076 | Let me put this another way: imagine a near future where Boost.Thread has been rewritten atop of the STL11 enhancing the STL11 threading facilities very substantially with lots of cool features which may not enter the standard until the 2020s. If your library is hardcoded to only use the STL, you may lose out on substantial performance or feature improvements. Your users may clamour to be able to use Boost.Thread with your library. You will then have to add an additional code path for Boost.Thread which replicates the STL11 threading path, probably selectable using a macro and the alternative code paths swapped out with `#ifdef`. But you still may not be done - what if Boost.Chrono also adds significant new features? Or Boost.Regex? Or any of the Boost libraries now standardised into the STL? Before you know it [https://github.com/chriskohlhoff/asio/blob/master/asio/include/asio/detail/config.hpp your config.hpp may look like the one from ASIO] which has already gone all the way in letting users choose their particular ASIO configuration, and let me quote a mere small section of it to give an idea of what is involved: |
| 1077 | |
| 1078 | {{{#!c++ |
| 1079 | ... |
| 1080 | // Standard library support for chrono. Some standard libraries (such as the |
| 1081 | // libstdc++ shipped with gcc 4.6) provide monotonic_clock as per early C++0x |
| 1082 | // drafts, rather than the eventually standardised name of steady_clock. |
| 1083 | #if !defined(ASIO_HAS_STD_CHRONO) |
| 1084 | # if !defined(ASIO_DISABLE_STD_CHRONO) |
| 1085 | # if defined(__clang__) |
| 1086 | # if defined(ASIO_HAS_CLANG_LIBCXX) |
| 1087 | # define ASIO_HAS_STD_CHRONO 1 |
| 1088 | # elif (__cplusplus >= 201103) |
| 1089 | # if __has_include(<chrono>) |
| 1090 | # define ASIO_HAS_STD_CHRONO 1 |
| 1091 | # endif // __has_include(<chrono>) |
| 1092 | # endif // (__cplusplus >= 201103) |
| 1093 | # endif // defined(__clang__) |
| 1094 | # if defined(__GNUC__) |
| 1095 | # if ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 6)) || (__GNUC__ > 4) |
| 1096 | # if defined(__GXX_EXPERIMENTAL_CXX0X__) |
| 1097 | # define ASIO_HAS_STD_CHRONO 1 |
| 1098 | # if ((__GNUC__ == 4) && (__GNUC_MINOR__ == 6)) |
| 1099 | # define ASIO_HAS_STD_CHRONO_MONOTONIC_CLOCK 1 |
| 1100 | # endif // ((__GNUC__ == 4) && (__GNUC_MINOR__ == 6)) |
| 1101 | # endif // defined(__GXX_EXPERIMENTAL_CXX0X__) |
| 1102 | # endif // ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 6)) || (__GNUC__ > 4) |
| 1103 | # endif // defined(__GNUC__) |
| 1104 | # if defined(ASIO_MSVC) |
| 1105 | # if (_MSC_VER >= 1700) |
| 1106 | # define ASIO_HAS_STD_CHRONO 1 |
| 1107 | # endif // (_MSC_VER >= 1700) |
| 1108 | # endif // defined(ASIO_MSVC) |
| 1109 | # endif // !defined(ASIO_DISABLE_STD_CHRONO) |
| 1110 | #endif // !defined(ASIO_HAS_STD_CHRONO) |
| 1111 | |
| 1112 | // Boost support for chrono. |
| 1113 | #if !defined(ASIO_HAS_BOOST_CHRONO) |
| 1114 | # if !defined(ASIO_DISABLE_BOOST_CHRONO) |
| 1115 | # if (BOOST_VERSION >= 104700) |
| 1116 | # define ASIO_HAS_BOOST_CHRONO 1 |
| 1117 | # endif // (BOOST_VERSION >= 104700) |
| 1118 | # endif // !defined(ASIO_DISABLE_BOOST_CHRONO) |
| 1119 | #endif // !defined(ASIO_HAS_BOOST_CHRONO) |
| 1120 | ... |
| 1121 | }}} |
| 1122 | |
| 1123 | ASIO currently has over 1000 lines of macro logic in its config.hpp with at least twelve different possible combinations, so that is 2 ^ 12 = 4096 different configurations of code paths (note some combinations may not be allowed in the source code, I didn't check). Are all of these tested equally? I actually don't know, but it seems a huge task requiring many days of testing if they are. However there is a far worse problem here: what happens if library A configures ASIO one way and library B configures ASIO a different way, ''and then a user combines both libraries A and B into the same process?'' |
| 1124 | |
| 1125 | The answer is that such a combination violates ODR, and therefore is undefined behaviour i.e. it will crash. This makes the ability to so finely configure ASIO much less useful than it could be. |
| 1126 | |
| 1127 | Let me therefore propose something better: allow library users to ''dependency inject'' from the outside the configuration of whether to use a STL11 dependency or its Boost equivalent. If one makes sure to encapsulate the dependency injection into a unique inline namespace, that prevents violation of ODR and therefore collision of the incompatibly configured library dependencies. If the dependent library takes care to coexist with alternative configurations and versions of itself inside the same process, this offers maximum convenience and utility to your library's users. |
| 1128 | |
| 1129 | |
| 1130 | Sometimes multiple code paths are unavoidable, specifically when you make use of Boost enhanced APIs not yet in the STL. However a large amount of the time one can treat the STL11 and the Boost implementation as being ''perfectly substitutable'' for one another, so now all you need is a method for your library's users to: |