{"action":"created","issue":{"url":"https://api.github.com/repos/CaseyCarter/stl2/issues/18","labels_url":"https://api.github.com/repos/CaseyCarter/stl2/issues/18/labels{/name}","comments_url":"https://api.github.com/repos/CaseyCarter/stl2/issues/18/comments","events_url":"https://api.github.com/repos/CaseyCarter/stl2/issues/18/events","html_url":"https://github.com/CaseyCarter/stl2/issues/18","id":112657527,"number":18,"title":"Semantics of initialization","user":{"login":"CaseyCarter","id":456873,"avatar_url":"https://avatars.githubusercontent.com/u/456873?v=3","gravatar_id":"","url":"https://api.github.com/users/CaseyCarter","html_url":"https://github.com/CaseyCarter","followers_url":"https://api.github.com/users/CaseyCarter/followers","following_url":"https://api.github.com/users/CaseyCarter/following{/other_user}","gists_url":"https://api.github.com/users/CaseyCarter/gists{/gist_id}","starred_url":"https://api.github.com/users/CaseyCarter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/CaseyCarter/subscriptions","organizations_url":"https://api.github.com/users/CaseyCarter/orgs","repos_url":"https://api.github.com/users/CaseyCarter/repos","events_url":"https://api.github.com/users/CaseyCarter/events{/privacy}","received_events_url":"https://api.github.com/users/CaseyCarter/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":4,"created_at":"2015-10-21T19:04:11Z","updated_at":"2015-11-12T21:49:52Z","closed_at":null,"body":"Q: What are the semantics of these functions:\r\n\r\n```c++\r\nSemiregular{T}\r\nauto f() {\r\n  T t;\r\n  return t;\r\n}\r\n\r\nSemiregular{T}\r\nauto g() { return T{}; }\r\n```\r\nWhen `T` is:\r\n1. `std::vector<int>`\r\n2. `int`\r\n3. `struct Foo { int i; Foo() {} };`\r\n\r\nA:\r\n1. both functions return an empty vector\r\n2. `g` returns `0`, `f` has undefined behavior\r\n3. both `f` and `g` have undefined behavior\r\n\r\nAre we happy with these semantics? 1 is of course perfect, and 2 is reasonable - I have no problem with default-initialization resulting in partially-formed values so as to be as efficient as possible - but to me 3 seems pathological and makes it impossible to write well-defined generic code.\r\n\r\nThis issue of course generalizes to any concept that includes initialization, e.g. in:\r\n\r\n```c++\r\ntemplate <class T>\r\nconcept bool C = requires MoveConstructible<T>() && Constructible<T, int, int>();\r\n\r\nC{T}\r\nauto f() { return T{42, 42}; }\r\n```\r\n\r\n`f` has the same problem: there's no way to determine if the value constructed by `T{42, 42}` is in the domain of move construction.\r\n\r\nHow do we specify e.g. `vector` to avoid this issue? Operations that can cause reallocation must require that all elements currently contained in the `vector` are in the domain of allocator-move-construction:\r\n\r\n> An object `v` of type `vector<T, A>` is *reallocatable* if `std::allocator_traits<A>::construct(a, p, std::move(e))` is well-defined where `a` equals `v.get_allocator()`, `p` points into a block of memory previously allocated by an allocator equal to `a`, and `e` refers to any element of `v`.\r\n\r\n```c++\r\ntemplate <class A, class T>\r\nconcept bool Allocator() {\r\n  return requires {\r\n    typename std::allocator_traits<A>::value_type;\r\n    requires Same<T, typename std::allocator_traits<A>::value_type>();\r\n    typename std::allocator_traits<A>::pointer;\r\n    requires Same<T, typename std::pointer_traits<\r\n      typename std::allocator_traits<A>::pointer>::element_type>();\r\n    typename std::allocator_traits<A>::size_type;\r\n    requires UnsignedIntegral<typename std::allocator_traits<A>::size_type>();\r\n  } && requires (A& a, typename std::allocator_traits<A>::pointer p,\r\n                 typename std::allocator_traits<A>::size_type n) {\r\n    { std::allocator_traits<A>::allocate(a, n) } -> decltype(p); // not required to be equality preserving\r\n    (void)std::allocator_traits<A>::deallocate(p, n);\r\n  };\r\n}\r\n\r\ntemplate <class A, class T>\r\nconcept bool AllocatorDestructible() {\r\n  return requires (A& a, T* p) {\r\n    std::allocator_traits<A>::destroy(a, p);\r\n  };\r\n}\r\n\r\ntemplate <class A, class T, class...Args>\r\nconcept bool AllocatorConstructible() {\r\n  return AllocatorDestructible<A, T>() && requires (A& a, T* p, Args&&...args) {\r\n    std::allocator_traits<A>::construct(a, p, std::forward<Args>(args)...);\r\n  };\r\n}\r\n\r\ntemplate <class A, class T>\r\nconcept bool AllocatorMoveConstructible() {\r\n  return AllocatorConstructible<A, T, T&&>();\r\n}\r\n\r\ntemplate <class A, class T>\r\nconcept bool AllocatorCopyConstructible() {\r\n  return AllocatorMoveConstructible<A, T>() &&\r\n    AllocatorConstructible<A, T, const T&>();\r\n}\r\n\r\nstruct reserve_t {};\r\n\r\ntemplate <class T, Allocator<T> A = std::allocator<T>>\r\nclass vector : A {\r\n  using traits = std::allocator_traits<A>;\r\npublic:\r\n  using pointer = typename traits::pointer;\r\n  using size_type = typename traits::size_type;\r\n\r\n  ~vector() requires AllocatorDestructible<A, T>() {\r\n    for (auto& i : *this) {\r\n      traits::destroy(allocator(), std::addressof(i));\r\n    }\r\n    traits::deallocate(allocator(), begin_, capacity());\r\n  }\r\n\r\n  vector() requires DefaultConstructible<A>() = default;\r\n  vector(reserve_t,  size_type n) requires DefaultConstructible<A>()\r\n    : begin_{traits::allocate(allocator(), n)}, end_{begin_}, alloc_{begin_ + n} {}\r\n  vector(size_t n) requires DefaultConstructible<A>() && AllocatorConstructible<A, T>()\r\n    : vector(reserve_t{}, n)\r\n  {\r\n    while (n-- > 0) {\r\n      emplace_back_unchecked();\r\n    }\r\n  }\r\n\r\n  A get_allocator() const { return *this; }\r\n\r\n  // Requires: size() < capacity() or *this is reallocatable\r\n  template <class...Args>\r\n    requires AllocatorMoveConstructible<A, T>() &&\r\n      AllocatorConstructible<A, T, Args...>()\r\n  void emplace_back(Args&&...args) {\r\n    if (size() == capacity()) { /* reallocate */ }\r\n    emplace_back_unchecked(std::forward<Args>(args)...);\r\n  }\r\n\r\n  // Requires: size() < capacity() or *this is reallocatable\r\n  void push_back(const T& t) requires AllocatorCopyConstructible<A, T>() {\r\n    emplace_back(t);\r\n  }\r\n  // Requires: size() < capacity() or *this is reallocatable\r\n  void push_back(T&& t) requires AllocatorMoveConstructible<A, T>() {\r\n    emplace_back(std::move(t));\r\n  }\r\n\r\n  // Requires: n <= capacity() or *this is reallocatable\r\n  void resize(size_type n)\r\n    requires AllocatorMoveConstructible<A, T>() && AllocatorDefaultConstructible<A, T>();\r\n\r\nprivate:\r\n  pointer begin_ = nullptr;\r\n  pointer end_ = nullptr;\r\n  pointer alloc_ = nullptr;\r\n\r\n  A& allocator() & { return *this; }\r\n\r\n  // Requires: size() < capacity()\r\n  template <class...Args>\r\n    requires AllocatorConstructible<Allocator, T, Args...>()\r\n  void emplace_back_unchecked(Args&&...args) {\r\n    traits::construct(allocator(), end_, std::forward<Args>(args)...);\r\n    ++end_;\r\n  }\r\n};\r\n```\r\n"},"comment":{"url":"https://api.github.com/repos/CaseyCarter/stl2/issues/comments/156246817","html_url":"https://github.com/CaseyCarter/stl2/issues/18#issuecomment-156246817","issue_url":"https://api.github.com/repos/CaseyCarter/stl2/issues/18","id":156246817,"user":{"login":"CaseyCarter","id":456873,"avatar_url":"https://avatars.githubusercontent.com/u/456873?v=3","gravatar_id":"","url":"https://api.github.com/users/CaseyCarter","html_url":"https://github.com/CaseyCarter","followers_url":"https://api.github.com/users/CaseyCarter/followers","following_url":"https://api.github.com/users/CaseyCarter/following{/other_user}","gists_url":"https://api.github.com/users/CaseyCarter/gists{/gist_id}","starred_url":"https://api.github.com/users/CaseyCarter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/CaseyCarter/subscriptions","organizations_url":"https://api.github.com/users/CaseyCarter/orgs","repos_url":"https://api.github.com/users/CaseyCarter/repos","events_url":"https://api.github.com/users/CaseyCarter/events{/privacy}","received_events_url":"https://api.github.com/users/CaseyCarter/received_events","type":"User","site_admin":false},"created_at":"2015-11-12T21:49:52Z","updated_at":"2015-11-12T21:49:52Z","body":"IIRC, our wording says something to the effect that operations aren't required to be total, which leaves it to each individual user of a concept to specify requirements on values. This results in the mess that I tried to address upthread in my discussion about `vector` and requiring *reallocatable* for the operations that can reallocate. I think I can save the rest of the library a lot of pain by coming up with some simple \"default\" rules that place requirements on values depending on the constraints applied to their types by a declaration. E.g., `sort` with `less` as predicate requires `TotallyOrdered` so NaNs are inadmissible, but `sort` with `ordered_less` (I think?) is perfectly happy to sort arrays of doubles that include NaNs. And I think we can get that with a few more sentences in 17.5.1.3 so we need to pepper the rest of the library with seemingly redundant requirements on values.\r\n\r\nAnyway, I'm all over this, I'll devise some wording to discuss after I finish turning `Function` into `Callable` and firming up the `Movable`/`Swappable` relationship."}}