Last active
November 2, 2024 05:52
-
-
Save jwpeterson/ed14bcb668b89ff8268db493e75d853a to your computer and use it in GitHub Desktop.
Demonstrate usage of noncopyable class in std containers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
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
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 regularemplace
instead? You may also want to try with more than one compiler, just to see if you get a different result.