How to correctly shim functionality into std?

2019-07-15 06:28发布

问题:

I recently switched from the Microsoft compiler to GCC. Among many things, I noticed that std::make_unique became unavailable. This is apparently because make_unique is not part of C++11 standard, and Microsoft just happened to include it as an extension.

We are planning to move to C++14 soon, but in the mean while I wrote this "shim" function, and put it into std.

namespace std
{
    template<typename T, typename... TArgs>
    unique_ptr<T> make_unique(TArgs&&... args)
    {
        return std::unique_ptr<T>(new T(std::forward<TArgs>(args)...));
    }
}

This solved that problem on gcc. However, I imagine it would cause problems back at Microsoft's side, since it would be a duplicate definition.

Is there a way to correctly shim functionality into std, so that it wouldn't cause trouble on different compilers / C++ standards? I looked around and didn't come up with anything. I thought maybe something like an include guard, for specific standard functions?

#ifndef MAKE_UNIQUE_DEFINED
#define MAKE_UNIQUE_DEFINED
// Define make_unique
#endif

Sadly it seems std doesn't define include guards for specific functions. Is there anything else I can do to make this more correct and compiler / c++ standard agnostic?

回答1:

You can, actually. Just turn the two conditions you mentioned in your post to a conditional macro definition:

#if defined(_MSC_VER) && __cplusplus == 201103L
#  define MAKE_UNIQUE_DEFINED 1
#endif
  • _MSC_VER checks the TU is compiled with MSVC. It's one of their predefined macros. You can use it to further refine the check, since it's the encoded version number of MSVC.
  • 201103L is the value of __cplusplus when the TU is compiled as C++11 (this is standard, across platforms).
  • In case __cplusplus isn't correctly defined for you (because Microsoft), you can use the _MSVC_LANG macro in its stead.

The above can be used to wrap your "shim", as you originally planned. Or any other MSVC extension that pertains to these versions, as a matter of fact.


As an alternative, to avoid reopening the std namespace (a no-no). You could use namespacing to have the definition present in a safe place, and control how your program interprets it:

namespace extended_std {
  #ifdef MAKE_UNIQUE_DEFINED
    inline namespace
  #else
    namespace
  #endif
  shim {
     // your definition goes here
  }

  #ifndef MAKE_UNIQUE_DEFINED
  using std::make_unique;
  #endif
} 

The macro is then only there to control what extended_std::make_unique refers to. It either makes the namespace inline, pouring its content into the enclosing one. Or adds a using declaration for std::make_unique.



回答2:

Simply put, do not define new functionality into std. Define the function elsewhere and arrange to conditionally make it available per the other answer. Often for things like this, it is easier to get it from your own namespace on all platforms until all the compilers catch up. You will need to have some mechanism to manage the declarations and using statements for compatibility functions. (Another traditional approach is to use something like autoconfigure to sniff the compilation environment and custom build compatibility headers for your application. This is rather ugly but when done well it results in high levels of portability.)

For an industrial strength library which aims provide new C++ features in not quite up-to-date compilation environments, there is Abseil: https://github.com/abseil/abseil-cpp . The design decisions there are good to look at if you are interested in doing something robust for a significant project.



回答3:

There is a predefined macro for this purpose: __cpp_lib_make_unique
See feature test macros

The following should be perfectly portable:

#include <memory>
#include <utility>

#ifdef __cpp_lib_make_unique

namespace lib {
    using std::make_unique;
}

#else

namespace lib {
    template<typename T, typename... TArgs>
    std::unique_ptr<T> make_unique(TArgs&&... args)
    {
        return std::unique_ptr<T>(new T(std::forward<TArgs>(args)...));
    }
}

#endif

You end up having make_unique in another namespace, lib in this example, but that is necessary for strict conformance (see §17.4.3.1)

I say should because, unfortunately, MSVC does not define those macros, so you have to use _MSC_VER for MS specific things.



标签: c++ std