Technology advances by inventing new ways of doing things and by discarding old ways. The C++ Standardization Committee is simultaneously adding new features and removing old features at a gradual pace, because we’ve discovered thoroughly better ways of writing code. While feature removals can be annoying, in the sense that programmers need to go change old codebases in order to make them conform to new Standards, they’re also important. Feature removals simplify the Core Language and Standard Library, avoiding the doom of accreting complexity forever. Additionally, removing old features makes it easier to read and write code. C++ will always be a language that offers programmers many ways to write something, but by taking away inferior techniques, it’s easier to choose one of the remaining techniques which are more modern.
In the Visual C++ team, we’re trying to help programmers modernize their codebases and take advantage of new Standards, while avoiding unnecessary and untimely disruption. As Visual C++ itself is a multi-decade-old codebase, we understand how valuable legacy codebases are (as they’re the product of years of development and testing), and how difficult they can be to change. While we often post about new features and how to use them, this post will explain what the recently-finalized C++17 Standard has done with old features, and what to expect from future VS 2017 toolset updates. We want to make toolset updates as painless as possible, so that you can continue to compile your code in a compatible manner. When you’re ready, you can enable compiler options to begin migrating your code to new Standards (and away from non-Standard behavior), with additional compiler/library options to (temporarily!) disable disruptive new features, restore removed features, and silence deprecation warnings.
We recently implemented Standard version switches, long supported by other compilers, which allow programmers to migrate to newer Standards at their own pace. This means that we can be relatively more aggressive about implementing source breaking changes (including but not limited to feature removals and deprecations) when they’re guarded by the /std:c++17 and /std:c++latest switches, because they won’t affect the /std:c++14 default. (These switches do have a complexity cost, in that they increase the number of modes that the compiler can operate in.)
The C++ Standard follows a certain process to remove features. Typically (but not always), a feature is first “deprecated”. This is an official term which is essentially equivalent to the Committee making a frowny face at the feature. The Standardese for deprecated features is collected in a special section (Annex D) at the end of the document. While deprecated features remain Standard and must be supported by conforming implementations, deprecation puts the world on notice that removal is likely (but not guaranteed). (Note that implementations are allowed to warn about anything, but they can definitely warn about usage of deprecated features. The Standard now has an attribute for this very purpose, for marking code to emit such warnings.) In a following Standard, deprecated features can be removed outright.
If you’re curious, the relevant Standardese is D [depr]/2 “These are deprecated features, where deprecated is defined as: Normative for the current edition of this International Standard, but having been identified as a candidate for removal from future revisions. An implementation may declare library names and entities described in this section with the deprecated attribute (10.6.4).” and 10.6.4 [dcl.attr.deprecated]/1 “The attribute-token deprecated can be used to mark names and entities whose use is still allowed, but is discouraged for some reason. [ Note: In particular, deprecated is appropriate for names and entities that are deemed obsolescent or unsafe. -end note ]”.
Technically, even removal isn’t the end of the road for a feature. Implementations can conform to C++17, yet accept features that were removed in C++17, as an extension. For example, the STL’s Standardese has a “Zombie names” section, saying that “In namespace std, the following names are reserved for previous standardization”. Essentially, C++17 is saying that while it doesn’t specify auto_ptr or unary_function or so forth, conformant C++17 programs aren’t allowed to interfere with such names (e.g. with macros), so that conformant C++17 STL implementations can provide auto_ptr/etc. as a non-C++17-Standard extension. This allows implementers to choose whether they physically remove features, and additionally makes it easier for the Committee to remove features from the Standard.
So, in Visual C++’s C++17 mode, we’re implementing feature removals and deprecation warnings, with the intent of permanently removing features in the future (possibly the far future, but someday). Some of this was released in VS 2017 15.3. More is available in VS 2017 15.5 (the second toolset update), and you can expect deprecation and removal to continue indefinitely, as the Committee continues its work (e.g. std::rel_ops is hopefully doomed).
How You Can Help Accelerate C++17 Adoption
1a. Download the latest released version of VS (and use it in production), and/or
1b. Download the latest preview version of VS (and test it against your whole codebase), and/or
1c. Download the “daily” MSVC toolset build (and test it against your whole codebase).
2. Compile with /std:c++17 or /std:c++latest (at this moment, they enable identical features and are barely distinguishable via a macro, but they will diverge when we begin implementing C++20).
3. Report toolset bugs. We’re trying really hard to release new features in a solid state, limited only by ambiguity in the Standard itself, but C++ is complicated and we aren’t perfect, so there will be bugs.
4. Update your codebase to avoid removed and deprecated features, and react to other source breaking changes as new features are implemented. (For example, every time the STL introduces a new function like std::clamp() or std::reduce(), any codebases with “using namespace std;” directives and their own clamp/reduce/etc. identifiers can be broken.)
5. (Important!) It’s very likely that you’ll encounter source breaking changes in third-party libraries that you can’t modify (easily or at all). We try to provide escape hatches so you can restore removed features or silence deprecation warnings and get on with your work, but first, please report such issues to the relevant library maintainers. By helping them update their code, you’ll help many more C++ programmers just like you.
In the last couple of years, the Visual C++ team has started to build and test many open-source projects and libraries with our development toolsets and options like /std:c++17. We’re finding and reporting breaking changes ourselves, but we can’t build everything, so we could use your help.
Our Strategy For Deprecation And Removal
* In C++14 mode (the default), we warn about non-Standard machinery (e.g. std::tr1). These warnings can be silenced in a fine-grained manner.
* In C++17 mode, we remove non-Standard machinery (e.g. std::tr1). This machinery can be restored in a fine-grained manner. (It will then emit the deprecation warning, unless silenced.)
* In the STL’s next major binary-incompatible version (internally named “WCFB02”), we’ve permanently removed this non-Standard machinery (e.g. std::tr1).
* In C++14 mode (the default), we currently don’t warn about features that were deprecated in C++14 (e.g. auto_ptr, which was first deprecated in C++11), nor do we warn about features that were removed in C++17 (e.g. auto_ptr again, or std::function allocator support which was removed without being deprecated first). We reserve the right to add such warnings in the future, but we’re unlikely to do so.
* In C++17 mode, we remove features that were removed in the C++17 Standard (e.g. auto_ptr). They can be restored in a somewhat fine-grained manner, for now. Ideally, they will be permanently removed at some point in the future (e.g. first the default mode will switch from C++14 to C++17, then someday C++14 mode will be dropped entirely – at that point, legacy C++14-but-not-17 features like auto_ptr should be dropped entirely too).
* In C++17 mode, we warn about all Library features that were deprecated in the C++17 Standard (including features that were deprecated in previous Standards, like <strstream>), with one exception (D.5 [depr.c.headers] deprecates the <stdio.h> family, but we’re not going to warn about that). These C++17 deprecation warnings can be silenced in a fine-grained manner (basically, each section of Annex D can be independently silenced), or in a coarse-grained manner (silencing all C++17 deprecation warnings, but not other deprecation warnings).
* We expect to repeat this pattern for C++20 and beyond.
C++17 Feature Removals – Technical Details
* N4190 “Removing auto_ptr, random_shuffle(), And Old <functional> Stuff”
Implemented in VS 2017 15.3 (and earlier). Restored by defining _HAS_AUTO_PTR_ETC to 1 (hence “somewhat fine-grained” above).
auto_ptr was superseded by unique_ptr.
unary_function and binary_function were typically unnecessary. In the C++98/03 era, many user-defined function object classes derived from these base classes in an attempt to imitate STL conventions. However, STL containers and algorithms have never required such inheritance (or the typedefs that they provide). Only the function object “adaptors” (like bind1st()) needed such typedefs. Therefore, if you have classes deriving from unary_function or binary_function, you can probably eliminate the inheritance. Otherwise, you can provide the typedefs directly.
The binders bind1st() and bind2nd() were superseded by bind() and lambdas.
ptr_fun() is no longer necessary at all – modern machinery works with function pointers directly (and STL algorithms always have).
The mem_fun() family has been superseded by mem_fn(). Also, anything following the invoke() protocol (like std::function) works with pointers-to-members directly.
random_shuffle() was superseded by shuffle().
* P0004R1 “Removing Deprecated Iostreams Aliases”
Implemented in VS 2017 15.3 (and earlier). Restored by defining _HAS_OLD_IOSTREAMS_MEMBERS to 1. Unlikely to be encountered outside of STL test suites.
* P0003R5 “Removing Dynamic Exception Specifications”
Newly implemented in VS 2017 15.5. The Library part can be restored by defining _HAS_UNEXPECTED to 1.
* P0302R1 “Removing Allocator Support In std::function”, LWG 2385 “function::assign allocator argument doesn’t make sense”, LWG 2921 “packaged_task and type-erased allocators”, LWG 2976 “Dangling uses_allocator specialization for packaged_task”
Newly implemented in VS 2017 15.5. (LWG 2385 was previously implemented with a different macro.) Restored by defining _HAS_FUNCTION_ALLOCATOR_SUPPORT to 1, although it was neither robustly implemented nor portable to other implementations which didn’t even try (which proved to be the wiser course of action).
Non-Standard Feature Deprecations And Removals – Technical Details
* The non-Standard std::tr1 namespace and TR1-only machinery
Removal in C++17 mode was implemented in VS 2017 15.3 (and earlier). Restored by defining _HAS_TR1_NAMESPACE to 1.
Newly deprecated in VS 2017 15.5 with “warning STL4002: The non-Standard std::tr1 namespace and TR1-only machinery are deprecated and will be REMOVED. You can define _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING to acknowledge that you have received this warning.”
* The non-Standard std::identity struct
Removal in C++17 mode was implemented in VS 2017 15.3 (and earlier). Restored by defining _HAS_IDENTITY_STRUCT to 1.
Newly deprecated in VS 2017 15.5 with “warning STL4003: The non-Standard std::identity struct is deprecated and will be REMOVED. You can define _SILENCE_IDENTITY_STRUCT_DEPRECATION_WARNING to acknowledge that you have received this warning.”
* The non-Standard std::tr2::sys namespace
Newly deprecated in C++14 mode and removed in C++17 mode in VS 2017 15.5. Restored by defining _HAS_TR2_SYS_NAMESPACE to 1. Emits “warning STL4018: The non-Standard std::tr2::sys namespace is deprecated and will be REMOVED. It is superseded by std::experimental::filesystem. You can define _SILENCE_TR2_SYS_NAMESPACE_DEPRECATION_WARNING to acknowledge that you have received this warning.”
C++17 Feature Deprecations – Technical Details
These deprecation warnings are newly implemented in VS 2017 15.5. P0174R2 “Deprecating Vestigial Library Parts”, P0521R0 “Deprecating shared_ptr::unique()”, P0618R0 “Deprecating <codecvt>”, and other papers added these sections. (For example, P0005R4 “not_fn()” added a feature and deprecated not1(), not2(), and the result_type family of typedefs. Notably, P0604R0 “invoke_result, is_invocable, is_nothrow_invocable” was implemented in VS 2017 15.3, but its deprecation of result_of is newly implemented in VS 2017 15.5.)
As every warning message states, the coarse-grained macro for silencing is _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS . Here are the sections and their associated warning messages, where we tried to be extremely detailed and helpful:
D.4 [depr.cpp.headers]: “warning STL4004: <ccomplex>, <cstdalign>, <cstdbool>, and <ctgmath> are deprecated in C++17. You can define _SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.6 [depr.str.strstreams]: “warning STL4005: <strstream> is deprecated in C++17. You can define _SILENCE_CXX17_STRSTREAM_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.7 [depr.uncaught]: “warning STL4006: std::uncaught_exception() is deprecated in C++17. It is superseded by std::uncaught_exceptions(), plural. You can define _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.8.1 [depr.weak.result_type] and D.8.2 [depr.func.adaptor.typedefs]: “warning STL4007: Many result_type typedefs and all argument_type, first_argument_type, and second_argument_type typedefs are deprecated in C++17. You can define _SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.8.3 [depr.negators]: “warning STL4008: std::not1(), std::not2(), std::unary_negate, and std::binary_negate are deprecated in C++17. They are superseded by std::not_fn(). You can define _SILENCE_CXX17_NEGATORS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.9 [depr.default.allocator]: “warning STL4009: std::allocator<void> is deprecated in C++17. You can define _SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.9 [depr.default.allocator]: “warning STL4010: Various members of std::allocator are deprecated in C++17. Use std::allocator_traits instead of accessing these members directly. You can define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.10 [depr.storage.iterator]: “warning STL4011: std::raw_storage_iterator is deprecated in C++17. Consider using the std::uninitialized_copy() family of algorithms instead. You can define _SILENCE_CXX17_RAW_STORAGE_ITERATOR_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.11 [depr.temporary.buffer]: “warning STL4012: std::get_temporary_buffer() and std::return_temporary_buffer() are deprecated in C++17. You can define _SILENCE_CXX17_TEMPORARY_BUFFER_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.12 [depr.meta.types]: “warning STL4013: std::is_literal_type and std::is_literal_type_v are deprecated in C++17. You can define _SILENCE_CXX17_IS_LITERAL_TYPE_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.12 [depr.meta.types]: “warning STL4014: std::result_of and std::result_of_t are deprecated in C++17. They are superseded by std::invoke_result and std::invoke_result_t. You can define _SILENCE_CXX17_RESULT_OF_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.13 [depr.iterator.primitives]: “warning STL4015: The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. (The <iterator> header is NOT deprecated.) The C++ Standard has never required user-defined iterators to derive from std::iterator. To fix this warning, stop deriving from std::iterator and start providing publicly accessible typedefs named iterator_category, value_type, difference_type, pointer, and reference. Note that value_type is required to be non-const, even for constant iterators. You can define _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.14 [depr.util.smartptr.shared.obs]: “warning STL4016: std::shared_ptr::unique() is deprecated in C++17. You can define _SILENCE_CXX17_SHARED_PTR_UNIQUE_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
D.15 [depr.locale.stdcvt] and D.16 [depr.conversions]: “warning STL4017: std::wbuffer_convert, std::wstring_convert, and the <codecvt> header (containing std::codecvt_mode, std::codecvt_utf8, std::codecvt_utf16, and std::codecvt_utf8_utf16) are deprecated in C++17. (The std::codecvt class template is NOT deprecated.) The C++ Standard doesn’t provide equivalent non-deprecated functionality; consider using MultiByteToWideChar() and WideCharToMultiByte() from <Windows.h> instead. You can define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.”
Note that for all of the warning suppression macros, you must define them before any C++ Standard Library header has been included (both <vector> etc. and <cstdlib> etc.). This is because we’ve implemented the deprecation warnings with a system of macros that are initialized when the STL’s central internal header has been dragged in. Therefore, the best place to define the warning suppression macros is on the command line, project-wide, to ensure that they’re set before any headers are included. If you need to define several macros, you can use the /FI (Name Forced Include File) compiler option to force-include a header defining those macros, which will be processed before any include directives in source files.
Library Warning Suppression
The [[deprecated]] attribute emits compiler warning C4996, which can be given custom text. (As you can see above, we’re now numbering the STL’s warnings, to make them easier to search for.)
Note: As C4996 is shared by all deprecation warnings (both Standard deprecations and Microsoft deprecations), you should avoid disabling it globally unless there’s no other choice. For example, silencing “warning C4996: ‘std::copy::_Unchecked_iterators::_Deprecate’: Call to ‘std::copy’ with parameters that may be unsafe – this call relies on the caller to check that the passed values are correct. To disable this warning, use -D_SCL_SECURE_NO_WARNINGS. See documentation on how to use Visual C++ ‘Checked Iterators'” should be done via the fine-grained macro mentioned, and not via /wd4996 passed to the compiler (which would also suppress the C++17 deprecation warnings here).
However, library code sometimes needs to do things that would trigger deprecation warnings, even though it shouldn’t really count as a use of deprecated technology. This occurs within the STL itself. For example, allocator_traits needs to ask whether UserAlloc::pointer exists (providing a fallback if it doesn’t exist). It’s possible for UserAlloc to derive from std::allocator which provides a C++17-deprecated “pointer” typedef. While deriving from std::allocator isn’t a great idea, it can be done conformantly. Giving such a derived class to allocator_traits shouldn’t trigger the “std::allocator<T>::pointer is deprecated” warning, because the programmer-user didn’t even mention that typedef.
Therefore, when inspecting types for nested typedefs like this, we locally suppress warning C4996, like this:
#pragma warning(push)
#pragma warning(disable: 4996) // was declared deprecated
template<class _Ty>
struct _Get_pointer_type<_Ty, void_t<typename _Ty::pointer>>
{ // get _Ty::pointer
using type = typename _Ty::pointer;
};
#pragma warning(pop)
While this technique should be used sparingly, this is how third-library libraries can avoid triggering deprecation warnings, without requiring programmer-users to silence them throughout their entire projects.