2

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.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    "*in C++20, it'll need to be std::span instead*" Why does it *need* to be? Your code is not just going to stop working just because there's an alternate `span` type you could have used. As for `experimental::optional`, that's not even *required* to be implemented in C++14 implementations. – Nicol Bolas Oct 04 '18 at 18:55
  • 2
    Related codereview: https://codereview.stackexchange.com/q/136350/49895 – Justin Oct 04 '18 at 18:55
  • Any reason why you can't use macros? ABI compatibility? You're probably hosed if you need ABI compatibility, but who knows, someone may surprise me... – Brian Bi Oct 04 '18 at 18:56
  • @NicolBolas: See edit. – einpoklum Oct 04 '18 at 19:12
  • I'd personally not do this in C++, but in the build system. I can have CMake detect what version is available and give me an appropriate macro which I can use to choose which to #include. – Justin Oct 04 '18 at 19:27
  • 1
    @Justin: But the inclusion macro still doesn't solve the whole problem. What would you write instead of `int foo(std::span sp)`? ... and suggest you write your answer as an answer to the question. – einpoklum Oct 04 '18 at 19:28

4 Answers4

2

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;
Martin York
  • 257,169
  • 86
  • 333
  • 562
1

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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • I appreciate your first point. I don't think it obviates the question, since you could take care and check for trivial copy-ability, or just use it so that it's not a big deal. About your C++20 example - that pollutes the global namespace, which I don't want to do if I'm writing a library. – einpoklum Oct 04 '18 at 19:22
  • @einpoklum: Then feel free to put the `using` declaration inside a namespace somewhere. The basic idea remains the same: include the appropriate file and create an alias for the type. – Nicol Bolas Oct 04 '18 at 20:21
0

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.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 1
    But who uses `mig`? At least @MartinYork's `stdx` suggestion is intuitive (for me, anyway) - "it's like std but not quite std". `mig` might just remind you of the Russian aircraft... – einpoklum Oct 04 '18 at 19:30
  • @einpoklum: you can obviously use whatever namespae name you consider suitable. Assuming multiple people use a similar approach it may be advisable to use some form of unique name to avoid conflicting aliases. – Dietmar Kühl Oct 06 '18 at 17:03
  • I was actually thinking that maybe a _common_ name might be better - and for collisions, either use some module or a single common stdx-namespace-filling header with an include guard. – einpoklum Oct 06 '18 at 21:42
0

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
einpoklum
  • 118,144
  • 57
  • 340
  • 684