Skip to content

Instantly share code, notes, and snippets.

@jwpeterson
Last active February 15, 2023 15:35
Show Gist options
  • Save jwpeterson/ed14bcb668b89ff8268db493e75d853a to your computer and use it in GitHub Desktop.
Save jwpeterson/ed14bcb668b89ff8268db493e75d853a to your computer and use it in GitHub Desktop.
Demonstrate usage of noncopyable class in std containers
// Compilation requires -std=c++17
// http://stackoverflow.com/questions/17603666/copy-move-requirements-for-the-key-value-types-in-a-stdmap
#include <map>
#include <vector>
struct foo
{
int i;
foo(int j) : i(j) {}
// make foo non-copyable
foo(const foo &) = delete; // copy ctor
foo & operator=(const foo &) = delete; // assignment op
// Use default move ctors. You have to declare these, otherwise the
// class will not have automatically generated move ctors.
// foo (foo && other) = default;
foo & operator=(foo &&) = default;
// Non-default version of move ctor. Use noexcept to maintain the
// "strong exception guarantee". This is especially important for
// classes which are to be used in std containers -- the container
// may instead use the copy constructor (assuming it's not deleted)
// if the move constructor is not marked noexcept! Neither GCC nor
// clang seem to care whether noexcept is used in this case...
foo (foo && other) noexcept :
i(std::move(other.i))
{}
};
// Required for foo to be used as a key in std::map
bool operator<(const foo & f1, const foo & f2)
{
return f1.i < f2.i;
}
int main(int argc, char ** argv)
{
// Test whether we can create a map where keys are non-copyable. We
// can, but the map itself cannot be copied (makes sense).
{
std::map<foo, int> f;
// std::map<foo, int> f2 = f; // compile error - can't copy map with uncopyable items
std::map<foo, int> f3 = std::move(f); // OK: when moving, nothing is copied
}
// Test whether we can create a map where values are non-copyable.
{
std::map<int, foo> f;
std::map<int, foo> f3 = std::move(f); // OK: when moving, nothing is copied
}
// Test inserting noncopyable keys into the map.
{
std::map<foo, int> f;
f.insert(std::make_pair(foo(1), 1)); // works if foo has move ctor
f[foo(1)] = 1; // works if foo has move ctor
f.emplace(std::make_pair(foo(1), 1)); // works if foo has move ctor
f.emplace(foo(1), 1); // works if foo has move ctor
f.emplace_hint(/*hint=*/f.begin(), foo(1), 1);
f.try_emplace(foo(1), 1);
f.try_emplace(/*hint=*/f.begin(), foo(1), 1);
}
// Test non-copyable items in std::vector.
{
std::vector<foo> f;
f.push_back(foo(1)); // works if foo has move ctor
f.emplace_back(foo(1)); // works if foo has move ctor
f.emplace_back(1); // works by forwarding the arg to the foo ctor
}
return 0;
}
@jwpeterson
Copy link
Author

In the gist I did not test calling try_emplace for a non-copyable value_type. Does your code work if you change it to call regular emplace instead? You may also want to try with more than one compiler, just to see if you get a different result.

@vpshastry
Copy link

I figured out the problem. The Type1 and Type2 are provided by a header file. Like API 1's type is Type1 and API 2's type is Type2 etc. I opened the header file to look and these types are type aliases / template types. Unfortunately some of the base types for these types are same, out of many. For example

using Type1 = uint64_t;
using Type2 = uint64_t;

This was confusing the std::map::try_emplace with their template condition __exactly_once. So, neither did the std::in_place_type_t based constructor (6th in https://en.cppreference.com/w/cpp/utility/variant/variant) work. The final solution is to use std::in_place_index_t based constructor and use the specific index to emplace.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment