8
\$\begingroup\$

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
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Overall this sounds like a reasonable approach, you're not the first that goes through lists of compiler versions to check for feature support, looks like the standard defines some support macros to check for feature, have you considered using these ? Also don't know if you have considered using cmake to make some of these decisions at project creation time rather than at build time. This way you might be able to forgo the detection include file.

I think you're missing closing braces at the end of your namespace blocks.

I'm not sure about using numerical values for the, it probably doesn't matter when everything is done automatically. If the user wants to drive it through a -D from the outside then you get a magic number like use -D DLU_PORT_DETECT=2 rather than a more comprehensible -D DLU_USE_EXPERIMENTAL. But I realize this also has an effect on the detection logic, you don't have to use || to check over multiple defined symbols.

I hadn't seen inline namespaces before, but after reading up a bit, it almost sounds like you should either completely skip the detection_idiom namespace, or label it specifically to the implementation i.e. std, experimental or boost. They way you have it written up it disappears and hides the actually implementation for all three choices, i.e. Dlugosz::d3::is_detected is equivalent to Dlugosz::d3::detection_idiom::is_detected for all three implementations. Especially from (the C++ FAQ ) it looks like inline namespaces are intended to hide the current implementation but enable a differentiation when necessary by using the same inline namespace you disable this differentiation. By naming the inline namespaces with the implementation you could let the users specifically refer to an implementation. E.g. they want to make sure that is_detected comes from std by writing Dlugosz::d2::std::is_detected. If that is helpful i don't know, but naming the inline namespace the same for all implementations doesn't seem to add anything. Please correct me if I'm wrong.

\$\endgroup\$
5
  • \$\begingroup\$ ⟪missing closing braces⟫ The nested open only uses one to close as well. It compiles; any munged line is a paste error — I updated the post after building for real, since there were no answers yet. \$\endgroup\$
    – JDługosz
    Apr 30, 2018 at 16:19
  • \$\begingroup\$ ⟪a magic number like use -D DLU_PORT_DETECT=2⟫ I just tried it to be sure: using DLU_PORT_CHOICE_EXPERIMENTAL in a #define issued before the nested include defining that will still work, doing macro substitution when the symbol is used in the #if, not when it is first defined. \$\endgroup\$
    – JDługosz
    Apr 30, 2018 at 16:24
  • \$\begingroup\$ ⟪looks like the standard defines some support macros to check for feature, have you considered using these ?⟫ Yes! And that’s explained in the text why it didn’t work. (look for the bold “but”). I’ll add one by name as example, to make it more obvious what I was talking about. \$\endgroup\$
    – JDługosz
    Apr 30, 2018 at 16:27
  • \$\begingroup\$ uhm yea ... totally missed that one ... \$\endgroup\$ Apr 30, 2018 at 16:29
  • \$\begingroup\$ The use of the named but inline namespace allows a consumer of the d3 library to import just this set of names as a unit. Other code in the d3 (or nested under that) will see them as ambient. The user should not care which implementation is chosen — that is the point. If you want to use a specific implementation, just use it the normal way. \$\endgroup\$
    – JDługosz
    Apr 30, 2018 at 16:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.