12

I work on a large software application that must run on several platforms. Some of these platforms support some features of C++11 (e.g. MSVS 2010) and some don't support any (e.g. GCC 4.3.x). I expect this situation to continue for for several years (my best guess: 3-5 years).

Given that, I would like set up a compatibility interface such that (to whatever degree possible) people can write C++11 code that will still compile with older compilers with a minimum of maintenance. Overall, the goal is to minimize #ifdef's as much as reasonably possible while still enabling basic C++11 syntax/features on the platforms that support them, and provide emulation on the platforms that don't.

Let's start with std::move(). The most obvious way to achieve compatibility would be to put something like this in a common header file:

#if !defined(HAS_STD_MOVE)
namespace std { // C++11 emulation
  template <typename T> inline T& move(T& v) { return v; }
  template <typename T> inline const T& move(const T& v) { return v; }
}
#endif // !defined(HAS_STD_MOVE)

This allows people to write things like

std::vector<Thing> x = std::move(y);

... with impunity. It does what they want in C++11 and it does the best it can in C++03. When we finally drop the last of the C++03 compilers, this code can remain as is.

However, according to the standard, it is illegal to inject new symbols into the std namespace. That's the theory. My question is: practically speaking is there any harm in doing this as a way of achieving forward compatibility?

4
  • 1
    Boost already provides quite a bit of this, and already has code to use new features when/where available, so you might be able to just use what Boost provides, and be done with it. Of course, there are limitations -- most new features were added specifically because library-based solutions aren't adequate. Apr 3, 2012 at 20:38
  • yes, I am thinking specifically about those features that can be implemented at the library level, not the syntactical changes. Boost doesn't really address the issue of (seamless) forward compatibility. Unless I'm missing something...
    – mcmcc
    Apr 3, 2012 at 21:24
  • Gcc 4.3 already has a good handful of C++11 features, the Rvalue-references being probably the most important.
    – Jan Hudec
    Apr 4, 2012 at 6:38
  • @JanHudec: You're right. Poor example. In any case, there are other compilers that definitely don't support the syntax (e.g. whatever version of IBM's C++ compiler we have).
    – mcmcc
    Apr 5, 2012 at 13:46

3 Answers 3

9

I've been working for a good while in keeping a level of forwards- and backwards-compatibility in my C++ programs, until I eventually had to make a library toolkit out of it, which I'm preparing for release has already been released. In general, so long as you accept that you won't get "perfect" forwards-compatibility neither in features (some things just can't be forward-emulated) not in syntax (you probably will have to use macros, alternate namespaces for some things) then you are all set.

There's a good lot of features that can be emulated in C++03 in a level that is enough for practical use - and without all the hassle that comes with eg.: Boost. Heck, even the C++ standards proposal for nullptr suggests a C++03 backport. And then there's TR1 for example for everything C++11‑but‑we've‑had‑previews‑for‑years stuff. Not only that, some C++14 features like assert variants, transparent functors and optional can be implemented in C++03!

The only two things that I know that can not absolutely be backported are constexpr and variadic templates.

With regards to the entire matter of adding stuff to namespace std, my view of it is that it doesn't matter - at all. Think of Boost, one of the most important and relevant C++ libraries, and their implementation of TR1: Boost.Tr1. If you want to improve C++, make it forwards compatible with C++11, then by definition you are turning it into something that is not C++03, so blocking yourself over a Standard that you intend to avoid or leave behind anyway is, simply put, counterproductive. Purists will complain, but by definition one needs not to care about them.

Of course, just because you won't be following the (03) Standard after all doesn't mean you can't try to, or would be going to gleefully go around breaking it. That's not the point. So long as you keep very careful control as to what is added to the std namespace, and have a control of the environments where your software is used (ie.: do testing!), there should not be any untratable harm at all. If possible, define everything in a separate namespace and only add using directives to namespace std so that you are not adding anything there beyond what "absolutely" needs to go in. Which, IINM, is more or less what Boost.TR1 does.


Update (2013): as the request of the original question and seeing some of the comments that I can not add to due to lack of rep, here is a list of C++11 and C++14 features and their degree of portability to C++03:

  • nullptr: fully implementable given the official Committee's backport; you'll probably have to provide some type_traits specializations as well so that it is recognized as a "native" type.
  • forward_list: fully implementable, though allocator support relies on what your Tr1 implmenentation can provide.
  • New algorithms (partition_copy, etc): fully implementable.
  • Container constructions from brace-sequences (eg.: vector<int> v = {1, 2, 3, 4};): fully implementable, though wordier than one would like.
  • static_assert: near-fully implementable when implemented as a macro (you'll only have to be careful with commas).
  • unique_ptr: near-fully implementable, but you'll also need support from calling code (for storing them in containers, etc); see the below though.
  • rvalue-references: near-fully implementable depending on how much you expect to get from them (eg.: Boost Move).
  • Foreach iteration: near-fully implementable, syntax will differ somewhat.
  • using local functions as arguments (for eg.: transform): near-fully implementable, but syntax will differ enough - for example, local functions are not defined at the call site but right before.
  • explicit conversion operators: implementable to practical levels (getting the conversion made explicit), see Imperfect C++'s "explicit_cast"; but integration with language features such as static_cast<> might be near-impossible.
  • argument forwarding: implementable to practical levels given the above on rvalue-references, but you'll need to provide N overloads to your functions taking forwardeable arguments.
  • move: implementable to practical levels (see the two aboves). Of course, you'd have to use modifier containers and objects to profit from this.
  • Scoped allocators: Not really implementable unless your Tr1 implementation can assist it.
  • multibyte character types: Not really implementable unless your Tr1 can support you. But for the intended purpose it's better to rely on a library specifically designed to deal with the matter, such as ICU, even if using C++11.
  • Variadic argument lists: implementable with some hassle, pay attention to argument forwarding.
  • noexcept: depends on your compiler's features.
  • New auto semantics and decltype: depends on your compiler's features - eg.: __typeof__.
  • sized integer types (int16_t, etc): depends on your compiler's features - or you can delegate to the Portable stdint.h.
  • type attributes: depends on your compiler's features.
  • Initializer list: Not implementable to my knowledge; however if what you want is to initialize containers with sequences, see the above on "container constructions".
  • Template Aliasing: Not implemementable to my knowledge, but it is an unneeded feature anyway, and we've had ::type in templates forever
  • Variadic templates: Not implementable to my knowledge; the close is template argument defaulting, which requires N specializations, etc.
  • constexpr: Not implementable to my knowledge.
  • Uniform initialization: Not implementable to my knowledge, but guaranteed default-constructor initialization can be implemented ala Boost's value-initialized.
  • C++14 dynarray: fully implementable.
  • C++14 optional<>: near-fully implementable so long as your C++03 compiler supports alignment setups.
  • C++14 transparent functors: near-fully implementable, but your client code will likely have to explicitly use eg.: std::less<void> to make it work.
  • C++14 new assert variants (such as assure): fully implementable if you want asserts, near-fully implementable if you want to enable throws instead.
  • C++14 tuple extensions (get tuple element by type): fully implementable, and you can even get it to fail to compile with the exact cases described in the feature proposal.

(Disclaimer: several of these features are implemented in my C++ backports library that I have linked above, so I think I know what I'm talking about when I say "fully" or "near-fully".)

6

This is fundamentally impossible. Consider std::unique_ptr<Thing>. If it was possible to emulate rvalue references as a library, it would not be a language feature.

5
  • 1
    I did say "to whatever degree possible". Clearly some features will have to be left behind #ifdef's or not used at all. std::move() happens to be one that you can support the syntax (albeit not the functionality).
    – mcmcc
    Apr 3, 2012 at 21:15
  • 2
    Actually the rvalue references proposal mentions a library-based solution!
    – Jan Hudec
    Apr 4, 2012 at 6:41
  • More specifically, it is possible to implement move semantics for particular class in C++03, so it should be possible to define std::unique_ptr there, but it's some other features of rvalue references cannot be implemented in C++03, so std::forward is not possible. The other thing is that the std::unique_ptr won't be useful, because the collections won't use the move semantics unless you replace them all.
    – Jan Hudec
    Apr 4, 2012 at 7:16
  • @JanHudec: It isn't possible to define unique_ptr. Look at the failings of auto_ptr. unique_ptr is practically the textbook example of a class whose semantics were fundamentally enabled by the language feature.
    – DeadMG
    Apr 4, 2012 at 19:48
  • @DeadMG: No, it's not unique_ptr that was fundamentally enabled by the language feature. It wouldn't be very useful without that feature though. because without perfect forwarding it wouldn't be usable in many cases and perfect forwarding does require that feature.
    – Jan Hudec
    Apr 5, 2012 at 12:30
2
  1. Gcc started introducing C++11 (still C++0x at that time) in 4.3. This table says it already has rvalue references and some other less used features (you have to specify -std=c++0x option to enable them).
  2. Many additions to standard library in C++11 were already defined in TR1 and GNU stdlibc++ provides them in std::tr1 namespace. So just do appropriate conditional using.
  3. Boost defines most of the TR1 functions and can inject them into the TR1 namespace if you don't have it (but VS2010 does and gcc 4.3 does as well if you use GNU stdlibc++).
  4. Putting anything in std namespace is an "undefined behaviour". That means the specification does not say what will happen. But if you know that on particular platform the standard library does not define something, just go ahead and define it. Just expect that you will have to check on each platform what you need and what you can define.
  5. The proposal for rvalue references, N1690 mentions how to implement move semantics in C++03. That could be used to substitute unique_ptr. However, it would not be too useful, because it relies on collections actually using the move semantics and the C++03 ones obviously won't.
1
  • 1
    You're correct about GCC but unfortunately, I also have to support other (non-GCC) compilers. Your #4 bullet is at the heart of the question I'm asking. #5 is interesting but I'm not looking to support move semantics (the copy optimization) on these older platforms but rather just "std::move()" as a compilable syntax.
    – mcmcc
    Apr 5, 2012 at 15:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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