Document Number:N4336
Date:
Revises:N4084
Editor: Jeffrey Yasskin
Google, Inc.
jyasskin@google.com

Working Draft, C++ Extensions for Library Fundamentals, Version 2

Note: this is an early draft. It’s known to be incomplet and incorrekt, and it has lots of bad formatting.

1

General

[general]
1.1

Scope

[general.scope]

This technical specification describes extensions to the C++ Standard Library (1.2). These extensions are classes and functions that are likely to be used widely within a program and/or on the interface boundaries between libraries written by different organizations.

This technical specification is non-normative. Some of the library components in this technical specification may be considered for standardization in a future version of C++, but they are not currently part of any C++ standard. Some of the components in this technical specification may never be standardized, and others may be standardized in a substantially changed form.

The goal of this technical specification is to build more widespread existing practice for an expanded C++ standard library. It gives advice on extensions to those vendors who wish to provide them.

1.2

Normative references

[general.references]

The following referenced document is indispensable for the application of this document. For dated references, only the edition cited applies. For undated references, the latest edition of the referenced document (including any amendments) applies.

  • ISO/IEC 14882:—1, Programming Languages — C++

ISO/IEC 14882:— is herein called the C++ Standard. References to clauses within the C++ Standard are written as "C++14 §3.2". The library described in ISO/IEC 14882:— clauses 17–30 is herein called the C++ Standard Library.

Unless otherwise specified, the whole of the C++ Standard's Library introduction (C++14 §17) is included into this Technical Specification by reference.

1.3

Namespaces, headers, and modifications to standard classes

[general.namespaces]

Since the extensions described in this technical specification are experimental and not part of the C++ standard library, they should not be declared directly within namespace std. Unless otherwise specified, all components described in this technical specification either:

  • modify an existing interface in the C++ Standard Library in-place,
  • are declared in a namespace whose name appends ::experimental::fundamentals_v2 to a namespace defined in the C++ Standard Library, such as std or std::chrono, or
  • are declared in a subnamespace of a namespace described in the previous bullet, whose name is not the same as an existing subnamespace of namespace std.
[ Example: This TS does not define std::experimental::fundamentals_v2::chrono because the C++ Standard Library defines std::chrono. This TS does not define std::pmr::experimental::fundamentals_v2 because the C++ Standard Library does not define std::pmr. end example ]

Each header described in this technical specification shall import the contents of std::experimental::fundamentals_v2 into std::experimental as if by

namespace std {
  namespace experimental {
    inline namespace fundamentals_v2 {}
  }
}

Unless otherwise specified, references to other entities described in this technical specification are assumed to be qualified with std::experimental::fundamentals_v2::, and references to entities described in the standard are assumed to be qualified with std::.

Extensions that are expected to eventually be added to an existing header <meow> are provided inside the <experimental/meow> header, which shall include the standard contents of <meow> as if by

#include <meow>

New headers are also provided in the <experimental/> directory, but without such an #include.

1.4

Future plans (Informative)

[general.plans]

This section describes tentative plans for future versions of this technical specification and plans for moving content into future versions of the C++ Standard.

The C++ committee intends to release a new version of this technical specification approximately every year, containing the library extensions we hope to add to a near-future version of the C++ Standard. Future versions will define their contents in std::experimental::fundamentals_v3, std::experimental::fundamentals_v4, etc., with the most recent implemented version inlined into std::experimental.

When an extension defined in this or a future version of this technical specification represents enough existing practice, it will be moved into the next version of the C++ Standard by removing the experimental::fundamentals_vN segment of its namespace and by removing the experimental/ prefix from its header's path.

1.5

Feature-testing recommendations (Informative)

[general.feature.test]

For the sake of improved portability between partial implementations of various C++ standards, WG21 (the ISO technical committee for the C++ programming language) recommends that implementers and programmers follow the guidelines in this section concerning feature-test macros. [ Note: WG21's SD-6 makes similar recommendations for the C++ Standard itself. end note ]

Implementers who provide a new standard feature should define a macro with the recommended name, in the same circumstances under which the feature is available (for example, taking into account relevant command-line options), to indicate the presence of support for that feature. Implementers should define that macro with the value specified in the most recent version of this technical specification that they have implemented. The recommended macro name is "__cpp_lib_experimental_" followed by the string in the "Macro Name Suffix" column.

Programmers who wish to determine whether a feature is available in an implementation should base that determination on the presence of the header (determined with __has_include(<header/name>)) and the state of the macro with the recommended name. (The absence of a tested feature may result in a program with decreased functionality, or the relevant functionality may be provided in a different way. A program that strictly depends on support for a feature can just try to use the feature unconditionally; presumably, on an implementation lacking necessary support, translation will fail.)

Table 2 — Significant features in this technical specification
Doc. No. Title Primary Section Macro Name Suffix Value Header
N4076 A proposal to add a generalized callable negator 2.2 not_fn 201406 <experimental/functional>
N4282 The World’s Dumbest Smart Pointer 3.2 observer_ptr 201411 <experimental/memory>
N4273 Uniform Container Erasure 4.1 erase_if 201411 <experimental/vector>
N4257 Delimited iterators 5.2 ostream_joiner 201411 <experimental/iterator>
N4061 Greatest Common Divisor and Least Common Multiple 6.1.2, 6.1.3 gcd_lcm 201411 <experimental/numeric>
2

Function objects

[func]
2.1

Header <experimental/functional> synopsis

[header.functional.synop]
#include <functional>

namespace std {
namespace experimental {
inline namespace fundamentals_v2 {

  // 2.2, Function template not_fn
  template <class F> unspecified not_fn(F&& f);

} // namespace fundamentals_v2
} // namespace experimental
} // namespace std
2.2

Function template not_fn

[func.not_fn]
template <class F> unspecified not_fn(F&& f);

In the text that follows:

  • FD is the type decay_t<F>,
  • fd is an lvalue of type FD constructed from std::forward<F>(f),
  • fn is a forwarding call wrapper created as a result of not_fn(f),
Requires:
is_constructible<FD, F>::value shall be true. fd shall be a callable object (C++14 §20.9.1).
Returns:
A forwarding call wrapper fn such that the expression fn(a1, a2, ..., aN) is equivalent to !INVOKE(fd, a1, a2, ..., aN) (C++14 §20.9.2).
Throws:
Nothing unless the construction of fd throws an exception.
Remarks:
The return type shall satisfy the requirements of MoveConstructible. If FD satisfies the requirements of CopyConstructible, then the return type shall satisfy the requirements of CopyConstructible. [ Note: This implies that FD is MoveConstructible. end note ]

[ Note: Function template not_fn can usually provide a better solution than using the negators not1 and not2 end note ]

3

Memory

[memory]
3.1

Header <experimental/memory> synopsis

[header.memory.synop]
#include <memory>

namespace std {
  namespace experimental {
  inline namespace fundamentals_v2 {

    // 3.2, Non-owning pointers
    template <class W> class observer_ptr;

    // 3.2.6, observer_ptr specialized algorithms
    template <class W>
    void swap(observer_ptr<W>&, observer_ptr<W>&) noexcept;
    template <class W>
    observer_ptr<W> make_observer(W*) noexcept;
    // (in)equality operators
    template <class W1, class W2>
    bool operator==(observer_ptr<W1>, observer_ptr<W2>);

    template <class W1, class W2>
    bool operator!=(observer_ptr<W1>, observer_ptr<W2>);
    template <class W>
    bool operator==(observer_ptr<W>, nullptr_t) noexcept;
    template <class W>
    bool operator!=(observer_ptr<W>, nullptr_t) noexcept;
    template <class W>
    bool operator==(nullptr_t, observer_ptr<W>) noexcept;
    template <class W>
    bool operator!=(nullptr_t, observer_ptr<W>) noexcept;
    // ordering operators
    template <class W1, class W2>
    bool operator<(observer_ptr<W1>, observer_ptr<W2>);
    template <class W1, class W2>
    bool operator>(observer_ptr<W1>, observer_ptr<W2>);
    template <class W1, class W2>
    bool operator<=(observer_ptr<W1>, observer_ptr<W2>);
    template <class W1, class W2>
    bool operator>=(observer_ptr<W1>, observer_ptr<W2>);

  } // inline namespace fundamentals_v2
  } // namespace experimental

// 3.2.7, observer_ptr hash support
template <class T> struct hash;
template <class T> struct hash<experimental::observer_ptr<T>>;

} // namespace std
3.2

Non-owning pointers

[memory.observer.ptr]

A non-owning pointer, known as an observer, is an object o that stores a pointer to a second object, w. In this context, w is known as a watched object. [ Note: There is no watched object when the stored pointer is nullptr. end note ] An observer takes no responsibility or ownership of any kind for its watched object, if any; in particular, there is no inherent relationship between the lifetimes of o and w.

Specializations of observer_ptr shall meet the requirements of a CopyConstructible and CopyAssignable type. The template parameter W of an observer_ptr shall not be a reference type, but may be an incomplete type.

[ Note: The uses of observer_ptr include clarity of interface specification in new code, and interoperability with pointer-based legacy code. end note ]

3.2.1

Class template observer_ptr overview

[memory.observer.ptr.overview]
namespace std {
namespace experimental {
inline namespace fundamentals_v2 {

  template <class W> class observer_ptr {
  public:
    // publish our template parameter and variations thereof
    using element_type = W;
    using pointer = add_pointer_t<W>;            // exposition-only
    using reference = add_lvalue_reference_t<W>; // exposition-only

    // 3.2.2, observer_ptr constructors
    // default c’tor
    constexpr observer_ptr() noexcept;

    // pointer-accepting c’tors
    constexpr observer_ptr(nullptr_t) noexcept;
    constexpr explicit observer_ptr(pointer) noexcept;

    // copying c’tors (in addition to compiler-generated copy c’tor)
    template <class W2> constexpr observer_ptr(observer_ptr<W2>) noexcept;

    // 3.2.3, observer_ptr observers
    constexpr pointer get() const noexcept;
    constexpr reference operator*() const;
    constexpr pointer operator->() const noexcept;
    constexpr explicit operator bool() const noexcept;

    // 3.2.4, observer_ptr conversions
    constexpr explicit operator pointer() const noexcept;

    // 3.2.5, observer_ptr modifiers
    constexpr pointer release() noexcept;
    constexpr void reset(pointer = nullptr) noexcept;
    constexpr void swap(observer_ptr&) noexcept;
  }; // observer_ptr<>

} // inline namespace fundamentals_v2
} // namespace experimental
} // namespace std
3.2.2

observer_ptr constructors

[memory.observer.ptr.ctor]
constexpr observer_ptr() noexcept;constexpr observer_ptr(nullptr_t) noexcept;
Effects:
Constructs an observer_ptr object that has no corresponding watched object.
Postconditions:
get() == nullptr.
constexpr explicit observer_ptr(pointer other) noexcept;
Postconditions:
get() == other.
template <class W2> constexpr observer_ptr(observer_ptr<W2> other) noexcept;
Postconditions:
get() == other.get().
Remarks:
This constructor shall not participate in overload resolution unless W2* is convertible to W*.
3.2.3

observer_ptr observers

[memory.observer.ptr.obs]
constexpr pointer get() const noexcept;
Returns:
The stored pointer.
constexpr reference operator*() const;
Requires:
get() != nullptr.
Returns:
*get().
Throws:
Nothing.
constexpr pointer operator->() const noexcept;
Returns:
get().
constexpr explicit operator bool() const noexcept;
Returns:
get() != nullptr.
3.2.4

observer_ptr conversions

[memory.observer.ptr.conv]
constexpr explicit operator pointer() const noexcept;
Returns:
get().
3.2.5

observer_ptr modifiers

[memory.observer.ptr.mod]
constexpr pointer release() noexcept;
Postconditions:
get() == nullptr.
Returns:
The value get() had at the start of the call to release.
constexpr void reset(pointer p = nullptr) noexcept;
Postconditions:
get() == p.
constexpr void swap(observer_ptr& other) noexcept;
Effects:
Invokes swap on the stored pointers of *this and other.
3.2.6

observer_ptr specialized algorithms

[memory.observer.ptr.special]
template <class W>
void swap(observer_ptr<W>& p1, observer_ptr<W>& p2) noexcept;
Effects:
p1.swap(p2).
template <class W> observer_ptr<W> make_observer(W* p) noexcept;
Returns:
observer_ptr<W>{p}.
template <class W1, class W2>
bool operator==(observer_ptr<W1> p1, observer_ptr<W2> p2);
Returns:
p1.get() == p2.get().
template <class W1, class W2>
bool operator!=(observer_ptr<W1> p1, observer_ptr<W2> p2);
Returns:
not (p1 == p2).
template <class W>
bool operator==(observer_ptr<W> p, nullptr_t) noexcept;template <class W>
bool operator==(nullptr_t, observer_ptr<W> p) noexcept;
Returns:
not p.
template <class W>
bool operator!=(observer_ptr<W> p, nullptr_t) noexcept;template <class W>
bool operator!=(nullptr_t, observer_ptr<W> p) noexcept;
Returns:
(bool)p.
template <class W1, class W2>
bool operator<(observer_ptr<W1> p1, observer_ptr<W2> p2);
Returns:
less<W3>()(p1.get(), p2.get()), where W3 is the composite pointer type (C++14 §5) of W1* and W2*.
template <class W>
bool operator>(observer_ptr<W> p1, observer_ptr<W> p2);
Returns:
p2 < p1.
template <class W>
bool operator<=(observer_ptr<W> p1, observer_ptr<W> p2);
Returns:
not (p2 < p1).
template <class W>
bool operator>=(observer_ptr<W> p1, observer_ptr<W> p2);
Returns:
not (p1 < p2).
3.2.7

observer_ptr hash support

[memory.observer.ptr.hash]
template <class T> struct hash<experimental::observer_ptr<T>>;

The template specialization shall meet the requirements of class template hash (C++14 §20.9.12). For an object p of type observer_ptr<T>, hash<observer_ptr<T>>()(p) shall evaluate to the same value as hash<T*>()(p.get()).

4

Containers

[container]
4.1

Uniform container erasure

[container.erasure]
4.1.1

Header synopsis

[container.erasure.syn]

For brevity, this section specifies the contents of 9 headers, each of which behaves as described by 1.3.

namespace std {
namespace experimental {
inline namespace fundamentals_v2 {

  // 4.1.2, Function template erase_if
  // 4.1.3, Function template erase

  // <experimental/string>
  template <class charT, class traits, class A, class Predicate>
    void erase_if(basic_string<charT, traits, A>& c, Predicate pred);
  template <class charT, class traits, class A, class U>
    void erase(basic_string<charT, traits, A>& c, const U& value);

  // <experimental/deque>
  template <class T, class A, class Predicate>
    void erase_if(deque<T, A>& c, Predicate pred);
  template <class T, class A, class U>
    void erase(deque<T, A>& c, const U& value);

  // <experimental/vector>
  template <class T, class A, class Predicate>
    void erase_if(vector<T, A>& c, Predicate pred);
  template <class T, class A, class U>
    void erase(vector<T, A>& c, const U& value);

  // <experimental/forward_list>
  template <class T, class A, class Predicate>
    void erase_if(forward_list<T, A>& c, Predicate pred);
  template <class T, class A, class U>
    void erase(forward_list<T, A>& c, const U& value);

  // <experimental/list>
  template <class T, class A, class Predicate>
    void erase_if(list<T, A>& c, Predicate pred);
  template <class T, class A, class U>
    void erase(list<T, A>& c, const U& value);

  // <experimental/map>
  template <class K, class T, class C, class A, class Predicate>
    void erase_if(map<K, T, C, A>& c, Predicate pred);
  template <class K, class T, class C, class A, class Predicate>
    void erase_if(multimap<K, T, C, A>& c, Predicate pred);

  // <experimental/set>
  template <class K, class C, class A, class Predicate>
    void erase_if(set<K, C, A>& c, Predicate pred);
  template <class K, class C, class A, class Predicate>
    void erase_if(multiset<K, C, A>& c, Predicate pred);

  // <experimental/unordered_map>
  template <class K, class T, class H, class P, class A, class Predicate>
    void erase_if(unordered_map<K, T, H, P, A>& c, Predicate pred);
  template <class K, class T, class H, class P, class A, class Predicate>
    void erase_if(unordered_multimap<K, T, H, P, A>& c, Predicate pred);

  // <experimental/unordered_set>
  template <class K, class H, class P, class A, class Predicate>
    void erase_if(unordered_set<K, H, P, A>& c, Predicate pred);
  template <class K, class H, class P, class A, class Predicate>
    void erase_if(unordered_multiset<K, H, P, A>& c, Predicate pred);

} // inline namespace fundamentals_v2
} // namespace experimental
} // namespace std
4.1.2

Function template erase_if

[container.erasure.erase_if]
template <class charT, class traits, class A, class Predicate>
void erase_if(basic_string<charT, traits, A>& c, Predicate pred);template <class T, class A, class Predicate>
void erase_if(deque<T, A>& c, Predicate pred);template <class T, class A, class Predicate>
void erase_if(vector<T, A>& c, Predicate pred);
Effects:
Equivalent to: c.erase(remove_if(c.begin(), c.end(), pred), c.end());
template <class T, class A, class Predicate>
void erase_if(forward_list<T, A>& c, Predicate pred);template <class T, class A, class Predicate>
void erase_if(list<T, A>& c, Predicate pred);
Effects:
Equivalent to: c.remove_if(pred);
template <class K, class T, class C, class A, class Predicate>
void erase_if(map<K, T, C, A>& c, Predicate pred);template <class K, class T, class C, class A, class Predicate>
void erase_if(multimap<K, T, C, A>& c, Predicate pred);template <class K, class C, class A, class Predicate>
void erase_if(set<K, C, A>& c, Predicate pred);template <class K, class C, class A, class Predicate>
void erase_if(multiset<K, C, A>& c, Predicate pred);template <class K, class T, class H, class P, class A, class Predicate>
void erase_if(unordered_map<K, T, H, P, A>& c, Predicate pred);template <class K, class T, class H, class P, class A, class Predicate>
void erase_if(unordered_multimap<K, T, H, P, A>& c, Predicate pred);template <class K, class H, class P, class A, class Predicate>
void erase_if(unordered_set<K, H, P, A>& c, Predicate pred);template <class K, class H, class P, class A, class Predicate>
void erase_if(unordered_multiset<K, H, P, A>& c, Predicate pred);
Effects:
Equivalent to:
for (auto i = c.begin(), last = c.end(); i != last; ) {
  if (pred(*i)) {
    i = c.erase(i);
  } else {
    ++i;
  }
}
4.1.3

Function template erase

[container.erasure.erase]
template <class charT, class traits, class A, class U>
void erase(basic_string<charT, traits, A>& c, const U& value);template <class T, class A, class U>
void erase(deque<T, A>& c, const U& value);template <class T, class A, class U>
void erase(vector<T, A>& c, const U& value);
Effects:
Equivalent to: c.erase(remove(c.begin(), c.end(), value), c.end());
template <class T, class A, class U>
void erase(forward_list<T, A>& c, const U& value);template <class T, class A, class U>
void erase(list<T, A>& c, const U& value);
Effects:
Equivalent to: erase_if(c, [&](auto& elem) { return elem == value; });
[ Note: Overloads of erase() for associative containers and unordered associative containers are intentionally not provided. end note ]
5

Iterators library

[iterator]
5.1

Header <experimental/iterator> synopsis

[iterator.synopsis]
#include <iterator>

namespace std {
namespace experimental {
inline namespace fundamentals_v2 {

  // 5.2, Class template ostream_joiner
  template <class DelimT, class charT = char, class traits = char_traits<charT> >
      class ostream_joiner;
  template <class charT, class traits, class DelimT>
    ostream_joiner<decay_t<DelimT>, charT, traits>
    make_ostream_joiner(basic_ostream<charT, traits>& os, DelimT&& delimiter);

} // inline namespace fundamentals_v2
} // namespace experimental
} // namespace std
5.2

Class template ostream_joiner

[iterator.ostream.joiner]

ostream_joiner writes (using operator<<) successive elements onto the output stream from which it was constructed. The delimiter that it was constructed with is written to the stream between every two Ts that are written. It is not possible to get a value out of the output iterator. Its only use is as an output iterator in situations like

while (first != last)
  *result++ = *first++;

ostream_joiner is defined as

namespace std {
namespace experimental {
inline namespace fundamentals_v2 {

  template <class DelimT, class charT = char, class traits = char_traits<charT> >
  class ostream_joiner {
  public:
    typedef charT char_type;
    typedef traits traits_type;
    typedef basic_ostream<charT,traits> ostream_type;
    typedef output_iterator_tag iterator_category;
    typedef void value_type;
    typedef void difference_type;
    typedef void pointer;
    typedef void reference;

    ostream_joiner(ostream_type& s, DelimT&& delimiter);
    ostream_joiner(ostream_type& s, const DelimT& delimiter);
    template<typename T>
    ostream_joiner<DelimT, charT,traits>& operator=(const T& value);
    ostream_joiner<DelimT, charT,traits>& operator*();
    ostream_joiner<DelimT, charT,traits>& operator++();
    ostream_joiner<DelimT, charT,traits>& operator++(int);
  private:
    basic_ostream<charT,traits>* out_stream; // exposition only
    DelimT delim;                            // exposition only
    bool first_element;                      // exposition only
  };
} // inline namespace fundamentals_v2
} // namespace experimental
} // namespace std
5.2.1

ostream_joiner constructor

[iterator.ostream.joiner.cons]
ostream_joiner(ostream_type& s, const DelimT& delimiter);
Effects:
Initializes out_stream with &s, delim with delimiter, and first_element with true.
ostream_joiner(ostream_type& s, DelimT&& delimiter);
Effects:
Initializes out_stream with &s, delim with move(delimiter), and first_element with true.
5.2.2

ostream_joiner operations

[iterator.ostream.joiner.ops]
template<typename T>
ostream_joiner<DelimT, charT, traits>& operator=(const T& value);
Effects:
if (!first_element)
  *out_stream << delim;
first_element = false;
*out_stream << value;
return (*this);
ostream_joiner<DelimT, charT, traits>& operator*();
Returns:
*this
ostream_joiner<DelimT, charT, traits>& operator++();ostream_joiner<DelimT, charT, traits>& operator++(int);
Returns:
*this
5.2.3

ostream_joiner creation function

[iterator.ostream.joiner.creation]
template <class charT, class traits, class DelimT>
ostream_joiner<decay_t<DelimT>, charT, traits>
make_ostream_joiner(basic_ostream<charT, traits>& os, DelimT&& delimiter);
Returns:
ostream_joiner<decay_t<DelimT>, charT, traits>(os, forward<DelimT>(delimiter));
6

Numerics library

[numeric]
6.1

Generalized numeric operations

[numeric.ops]
6.1.1

Header <experimental/numeric> synopsis

[numeric.ops.overview]
#include <numeric>

namespace std {
namespace experimental {
inline namespace fundamentals_v2 {

  // 6.1.2, Greatest common divisor
  template<class M, class N>
  constexpr common_type_t<M,N> gcd(M m, N n);

  // 6.1.3, Least common multiple
  template<class M, class N>
  constexpr common_type_t<M,N> lcm(M m, N n);

} // inline namespace fundamentals_v2
} // namespace experimental
} // namespace std
6.1.2

Greatest common divisor

[numeric.ops.gcd]
template<class M, class N>
constexpr common_type_t<M,N> gcd(M m, N n);
Requires:
|m| shall be representable as a value of type M and |n| shall be representable as a value of type N. [ Note: These requirements ensure, for example, that gcd(m, m) = |m| is representable as a value of type M. end note ]
Remarks:
If either M or N is not an integer type, the program is ill-formed.
Returns:
zero when m and n are both zero. Otherwise, returns the greatest common divisor of |m| and |n|.
Throws:
Nothing.
6.1.3

Least common multiple

[numeric.ops.lcm]
template<class M, class N>
constexpr common_type_t<M,N> lcm(M m, N n);
Requires:
|m| shall be representable as a value of type M and |n| shall be representable as a value of type N. The least common multiple of |m| and |n| shall be representable as a value of type common_type_t<M,N>.
Remarks:
If either M or N is not an integer type, the program is ill-formed.
Returns:
zero when either m or n is zero. Otherwise, returns the least common multiple of |m| and |n|.
Throws:
Nothing.