Currently, some library features are not in std
that comes with the compiler, but may be in std::experimental
or may not be present at all but Boost has a version.
This is not a new problem: once upon a time it was std
, std::tr1
, or Boost. I expect the general idea will continue, with new libraries arriving on the bleeding edge as established ones finally become universally included with the compiler.
I want to write code that doesn’t need to worry about which compiler its on, or need to be updated throughout when the compiler is revised. Here is one feature prepared in a portability header, which (once I get it just right) will serve as the model for everything.
In general, I have to worry about
- which header to include
- which namespace the names can be found in
- what to link with
By making a portability header for a specific feature, the code includes that file, which itself includes the correct choice.
The names, wherever they came from, and placed in a namespace of my own. The code uses that.
It is further packaged inside a uniquely named namespace, so code outside of this library can just pull in that group of related symbols easily.
Any link-time dependency will be noted using the proper #pragma
based on the compiler (that is not needed in the following attempt).
I figure I’ll use a set of preprocessor symbols so the user can command the choices at compile time directly, overriding any auto-detection. In the file here, it is structured so the detection happens first. Then, in a second section, the choice is applied. The detection and implementation are independent and separated in the file.
Now, how do you detect in the first place? If it involves a new header I could use __has_include
, but updates to existing std headers don’t make that applicable.
Using metaprogramming, I can detect specific names in std
. But, the results of that do not feed back to the preprocessor! There are detection macros defined, but these tell of the existence in std::experimental
and won’t be seen in the final std
home. From the form of the symbols (e.g. __cpp_lib_experimental_detect
), it is clear they apply to the experimental header only, and the TS doesn’t define other symbols to indicate presence in the std
once adopted.
So the only solution is to check individual compiler, version, and platform versions and configuration options. I’ve populated the answer conservatively, letting it through only for compilers that have been tried. Others, even if filled in based on documentation, lead to a #error
so they must be vetted when actually used on that platform.
Any unknown configuration choice also gives an #error
.
Any other advice? Anything I missed?
#pragma once
#include <type_traits>
#define DLU_PORT_CHOICE_STD 1
#define DLU_PORT_CHOICE_EXPERIMENTAL 2
#define DLU_PORT_CHOICE_BOOST 3
// first, figure out which way to do it. It can be pre-specified as a #define or -D at compile time
#if !defined DLU_PORT_DETECT
// if specified by user directly, keep it.
#if defined DLU_PORT_ALL
// a wildcard that applies to all PORTs that are not otherwise explicitly set
# define DLU_PORT_DETECT DLU_PORT_ALL
#elif defined _MSC_VER
// The Microsoft compiler
#if _MSC_FULL_VER > 191426412 // the last one I tried
# error Check to see if detection idiom is avalable yet
#else
#define DLU_PORT_DETECT DLU_PORT_CHOICE_BOOST
#endif
// #elif other compiler brands go here *****
// if you get here, please update and send a pull request.
#endif
#endif
the second step:
// ===========
// Use the chosen option
#if DLU_PORT_DETECT == DLU_PORT_CHOICE_STD
# error Need to update file.
// this does not exist in the standard yet. When a compiler is observed to have it,
// put in the proper lines to match the actuality.
// == presumed ==
// <type_traits> already included
namespace Dlugosz::d3 {
inline namespace detection_idiom {
using std::is_detected;
using std::is_detected_v;
using std::detected_t;
using std::detected_or;
using std::detected_or_t;
using std::is_detected_exact;
using std::is_detected_exact_v;
using std::is_detected_convertable;
using std::is_detected_convertable_v;
#elif DLU_PORT_DETECT == DLU_PORT_CHOICE_EXPERIMENTAL
# include <experimental/type_traits>
namespace Dlugosz::d3 {
inline namespace detection_idiom {
using std::experimental::is_detected;
using std::experimental::is_detected_v;
using std::experimental::detected_t;
using std::experimental::detected_or;
using std::experimental::detected_or_t;
using std::experimental::is_detected_exact;
using std::experimental::is_detected_exact_v;
using std::experimental::is_detected_convertable;
using std::experimental::is_detected_convertable_v;
#elif DLU_PORT_DETECT == DLU_PORT_CHOICE_BOOST
# include <boost/type_traits/is_detected.hpp>
namespace Dlugosz::d3 {
inline namespace detection_idiom {
using boost::is_detected;
using boost::is_detected_v;
template <template<class...> class Op, class... Args>
using detected_t = typename is_detected<Op, Args...>::type;
// Boost (as of 1.67.0) does not include all the forms. Copied these definitions
// from n4562 §3.3.4, using Boost’s underlying helper and qualifying std:: type traits used.
template <class Default, template<class...> class Op, class... Args>
using detected_or = boost::detail::detector<Default, void, Op, Args...>;
template <class Default, template<class...> class Op, class... Args>
using detected_or_t = typename detected_or<Default, Op, Args...>::type;
template <class Expected, template<class...> class Op, class... Args>
using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;
template <class Expected, template<class...> class Op, class... Args>
constexpr bool is_detected_exact_v = is_detected_exact<Expected, Op, Args...>::value;
template <class To, template<class...> class Op, class... Args>
using is_detected_convertible = std::is_convertible<detected_t<Op, Args...>, To>;
template <class To, template<class...> class Op, class... Args>
constexpr bool is_detected_convertible_v = is_detected_convertible<To, Op, Args...>::value;
#else
# error Detection idiom configuration has bad value
#endif