What's an idiomatic way to refer to either std::whatever or not_yet_in_std::whatever?

218 Views Asked by At

I like spans, so I use gsl::span here and there. But - in C++20, it's going to be std::span instead*. I use std::optional, but for C++14 code, it needs to be std::experimental::optional. And so on.

What's an idiomatic and sort-of future-proof way to make the compile-time choice between these variants (sometimes perhaps more than two), so that my actual code can just use one sequence-of-tokens which compile into the correctly-chosen span, or optional, or other similar construct?

Note: I want to avoid polluting the global namespace.


* Well, technically I could use gsl::span later as well, but the idea in this question is to use what's in the standard once it's available, and the nearest alternative, before that.

4

There are 4 best solutions below

3
On

I usually use something like this:

#if some_kind_of_test_here_not_necessarily_a_macro
namespace stdx = std;
#elif some_other_test_here
namespace stdx = std::experimental;
#else
#error "Some Message"
#endif

Now in your code just use:

stdx::span  mySpan;
3
On

One approach is the use of a migration header containing suitable using aliases in a migration namespace, e.g:

#if __cplusplus < 201402L
#include <experimental/optional>
namespace mig {
    template <typename T>
    using optional = std::experimental::optional<T>;
}
#else
#include <optional>
namespace mig {
    template <typename T>
    using optional = std::optional<T>;
}
#endif

While migrating you’d include the corresponding header and use mig::optional<T> for your code which happily interacts with other code using the optional-du-jour. Once the compatibility concern has gone away you can replace you custom qualification whenever suitable. Note, however, that there are some differences between these definitions, i.e., you’ll need to stick to the common functionality.

2
On

This question is wrong-headed, because even if there was such a "sequence of tokens", there is no guarantee that the two alternatives behave the same.

Consider experimental::optional vs. std::optional. The latter, after a defect report on C++17, is required to be trivially copyable if T is trivially copyable. experimental::optional isn't. If you rely on that for your C++17 builds, you have no idea if it is going to work against C++14.

gsl::span is less of a problem, since GSL implementations will likely track changes to std::span as it is incorporated into C++20.

However, if you insist on this, C++20 will make the feature test macros mandatory. So you can use macro techniques like this:

#include <version>
#ifdef <insert span test macro here>
#include <span>
template<typename T, std::ptrdiff_t N>
using span = std::span<T, N>;
#else
#include <gsl/span>
template<typename T, std::ptrdiff_t N>
using span = gsl::span<T, N>;
#endif

Of course, the problem here is that you have to include <version>, which itself is a C++20 header. So this code would only work with a compiler that is at least partially C++20 compliant.

0
On

An adaptation of @MartinYork's approach which (hopefully) works at the single-construct level rather than the entire-namespace level:

#if __cplusplus >= 202001L
#include <span>
namespace stdx {
template <class ElementType, std::ptrdiff_t Extent = std::dynamic_extent>
using span = std::span<ElementType, Extent>;
} // namespace stdx
#else
#include <gsl/span>
namespace stdx {
template <class ElementType, std::ptrdiff_t Extent = gsl::dynamic_extent>
using span = std::span<ElementType, Extent>;
} // namespace stdx
#endif // __cplusplus >= 202001L