Why can't I store an unordered_map with non-copyable value_type in a vector?

2,087 views
Skip to first unread message

Dirk Gerrits

unread,
Nov 23, 2012, 1:04:17 PM11/23/12
to std-dis...@isocpp.org
Hi,

I was wondering if someone could explain to me why GCC 4.7.2 and Clang 3.1 both reject the construct std::vector< std::unordered_map<K, T> > when T is a moveable but not copyable.  Is this behavior mandated by the standard, or are both compilers similarly bugged?  The code below demonstrates the problem.

#include <unordered_map>
#include <vector>

class move_only
{
public:
  move_only() {}

  move_only(move_only const&) = delete;
  move_only& operator=(move_only const&) = delete;

  move_only(move_only&&) = default;
  move_only& operator=(move_only&&) = default;
};

typedef std::unordered_map<int, move_only> naked_map;

class wrapped_map
{
public:
  wrapped_map() {}

  wrapped_map(wrapped_map const&) = delete;
  wrapped_map& operator=(wrapped_map const&) = delete;

  wrapped_map(wrapped_map&&) = default;
  wrapped_map& operator=(wrapped_map&&) = default;

private:
  naked_map m;
};

int main(int argc, char* argv[])
{
  // The following fails to compile using -std=c++11 in GCC 4.7.2 and Clang 3.1.
  // Their complaint is that std::pair<const int, move_only> has no copy
  // constructor.
  std::vector<naked_map> v1;
  v1.resize(10); // won't compile
  for (int i = 0; i != 10; ++i) {
    v1.emplace_back(); // won't compile
  }

  // This compiles just fine, however, for both compilers.
  std::vector<wrapped_map> v2;
  v2.resize(10);
  for (int i = 0; i != 10; ++i) {
    v2.emplace_back();
  }
}

Daniel Krügler

unread,
Nov 23, 2012, 2:50:17 PM11/23/12
to std-dis...@isocpp.org
2012/11/23 Dirk Gerrits <dirk.g...@gmail.com>

The problem is partially a QoI and partially a Library specification problem. In principle you are right to expect that resize should be callable for a move-only value type. This is in agreement what the resize spec says:

"T shall be MoveInsertable and DefaultInsertable into *this."

The wording also requires

"Remarks: If an exception is thrown other than by the move constructor of a non-CopyInsertable T
there are no effects."

This has the effect that an implementation is required to first check whether T has a no-throw move constructor: If it has a move constructor but if that one is not nothrow *and if the type is copyable*, the implications are that the type shall use the copy-insertion (to ensure that an exception would not have effects).

All if this is basically fine. Problem is that there is no requirement that std::unordered_map is nothrow, if value *and* predicate *and* hasher are nothrow. In addition, in the absence of a corresponding standard requirement there is no clear leeway that an implementation *may* decide to add a *conditional* noexcept specification to that effect (17.6.5.12 allows "An implementation may strengthen the exception-specification
for a non-virtual function by adding a non-throwing noexcept-specification." This can be read to only allow unconditional noexcept specifications).

I would suggest to write up a proposal that adds the requirement that the container types shall have conditional move constructor/assignment operator noexcept-specifications to realize more move-only support.

 
  for (int i = 0; i != 10; ++i) {
    v1.emplace_back(); // won't compile
  }

  // This compiles just fine, however, for both compilers.
  std::vector<wrapped_map> v2;

This trick works, because for a non-copyinsertable type (see above quote) the implementation cannot fall back to copy-construction when move-construction may throw. The result is that a failing move max have effects.

- Daniel
 
  v2.resize(10);
  for (int i = 0; i != 10; ++i) {
    v2.emplace_back();
  }
}

--
 
 
 

Howard Hinnant

unread,
Nov 23, 2012, 3:11:04 PM11/23/12
to std-dis...@isocpp.org

On Nov 23, 2012, at 1:04 PM, Dirk Gerrits <dirk.g...@gmail.com> wrote:

> I was wondering if someone could explain to me why GCC 4.7.2 and Clang 3.1 both reject the construct std::vector< std::unordered_map<K, T> > when T is a moveable but not copyable.

On Nov 23, 2012, at 2:50 PM, Daniel Krügler <daniel....@gmail.com> wrote:

> The problem is partially a QoI and partially a Library specification problem. In principle you are right to expect that resize should be callable for a move-only value type. This is in agreement what the resize spec says:
>
> "T shall be MoveInsertable and DefaultInsertable into *this."
>
> The wording also requires
>
> "Remarks: If an exception is thrown other than by the move constructor of a non-CopyInsertable T
> there are no effects."
>
> This has the effect that an implementation is required to first check whether T has a no-throw move constructor: If it has a move constructor but if that one is not nothrow *and if the type is copyable*, the implications are that the type shall use the copy-insertion (to ensure that an exception would not have effects).
>
> All if this is basically fine. Problem is that there is no requirement that std::unordered_map is nothrow, if value *and* predicate *and* hasher are nothrow. In addition, in the absence of a corresponding standard requirement there is no clear leeway that an implementation *may* decide to add a *conditional* noexcept specification to that effect (17.6.5.12 allows "An implementation may strengthen the exception-specification
> for a non-virtual function by adding a non-throwing noexcept-specification." This can be read to only allow unconditional noexcept specifications).

Fwiw, I didn't read it as to only allow unconditional noexcept specifications.

Dirk's code compiles with tip-of-trunk clang++/libc++.

> I would suggest to write up a proposal that adds the requirement that the container types shall have conditional move constructor/assignment operator noexcept-specifications to realize more move-only support.

I would support such a proposal. However it might be controversial to add even a conditional noexcept to the move constructor of node-based containers, unless the condition is allowed to be simply false. IICR, the VC++ node-based containers' move constructors need to allocate an end node.

Fwiw, all libc++ containers have a conditional noexcept on:

default constructor
move constructor
move assignment operator
swap

Howard

Daniel Krügler

unread,
Nov 23, 2012, 3:48:23 PM11/23/12
to std-dis...@isocpp.org
2012/11/23 Howard Hinnant <howard....@gmail.com>

On Nov 23, 2012, at 2:50 PM, Daniel Krügler <daniel....@gmail.com> wrote:
> Problem is that there is no requirement that std::unordered_map is nothrow, if value *and* predicate *and* hasher are nothrow. In addition, in the absence of a corresponding standard requirement there is no clear leeway that an implementation *may* decide to add a *conditional* noexcept specification to that effect (17.6.5.12 allows "An implementation may strengthen the exception-specification
> for a non-virtual function by adding a non-throwing noexcept-specification." This can be read to only allow unconditional noexcept specifications).

Fwiw, I didn't read it as to only allow unconditional noexcept specifications.

Yeah, the wording is presumably fuzzy enough to allow that. But I don't think that it is really clear. If we really want to allow *additional* (i.e. in extension to the standard specification) conditional noexcept-specifications, this needs corresponding wording and I'm not convinced that we really want to allow them without adding some further (minor) constraints upon implementations. The main reason is that if you add (by such an extension) a conditional noexcept-specification, you are always risking that the expression used as argument /may give and instantiation error/. You really cannot foresee this nor prevent that in general, because for some reason the observed type may instantiate yet non-instantiated members that may cause an error in that is not in the immediate context. If this happens you would loudly break code that (at least in theory) would otherwise work. I would say you can only add such conditional noexcept specifications if you can proof that irrespective of that concrete instantiation error this error would have happened even with the absence of your extension. I'm note sure yet how to prepare standardese from this at the moment, though ;-)

- Daniel

Nathan Ernst

unread,
Nov 23, 2012, 11:12:07 PM11/23/12
to std-dis...@isocpp.org, dirk.g...@gmail.com
I'm not sure it's relevant to this particular example, but I know that GCC 4.7.x has issues with std::pair & movable types and most associative containers that rely upon it.  This is also why all such containers miss the varying emplace methods.  From what I've read, I think (but cannot confirm) these issues should be fixed in GCC 4.8.

This GCC bugreport has most of the background of what I'm referring to:  http://gcc.gnu.org/bugzilla/show_bug.cgi?id=44436

Hope this helps.

Regards,
Nate

dirk.g...@gmail.com

unread,
Nov 24, 2012, 3:14:22 AM11/24/12
to std-dis...@isocpp.org
On Friday, November 23, 2012 8:50:18 PM UTC+1, Daniel Krügler wrote:
The problem is partially a QoI and partially a Library specification problem. In principle you are right to expect that resize should be callable for a move-only value type. [...]

Thank you for the quick clarification! :)
 
I would suggest to write up a proposal that adds the requirement that the container types shall have conditional move constructor/assignment operator noexcept-specifications to realize more move-only support.

While I would support such a change, I'm in no way qualified to write it up in standardese.
 
   std::vector<wrapped_map> v2;

This trick works, because for a non-copyinsertable type (see above quote) the implementation cannot fall back to copy-construction when move-construction may throw.

I had such a suspicion.  Would this be the recommended workaround for C++ implementations with this QoI issue?  Or is there a cleaner alternative?

- Dirk

dirk.g...@gmail.com

unread,
Nov 24, 2012, 3:16:50 AM11/24/12
to std-dis...@isocpp.org
On Friday, November 23, 2012 9:11:06 PM UTC+1, Howard Hinnant wrote:
Dirk's code compiles with tip-of-trunk clang++/libc++.

[...]

Fwiw, all libc++ containers have a conditional noexcept on:

default constructor
move constructor
move assignment operator
swap

Very nice!  So there is precedent, at least.

- Dirk
Reply all
Reply to author
Forward
0 new messages