Вот что я пытаюсь сделать и что я пробовал:
#include <memory>
#include <map>
#include <string>
using namespace std;
struct MyStruct {
const unique_ptr<int> a;
const unique_ptr<int> b;
};
int main() {
auto a = make_unique<int>(7);
auto b = make_unique<int>(5);
map<string, MyStruct> myMap;
myMap["ab"] = { move(a), move(b) }; // nope
myMap.insert("ab", { move(a), move(b) }); // nope
myMap.emplace("ab", { move(a), move(b) }); // nope
myMap.try_emplace("ab", { move(a), move(b) }); // nope
// EDIT 1:
myMap.emplace(piecewise_construct,
forward_as_tuple("ab"),
forward_as_tuple(move(a), move(b))
); // nope
// EDIT 2:
// works in C++20 as there is now a standard ctor for MyStruct:
myMap.try_emplace("ab", move(a), move(b));
return 0;
}
Я понимаю, почему ничего из этого не работает, но есть ли способ вставить элемент в myMap без изменения MyStruct?
const
элементы данных проблематичны, они несовместимы с семантикой значений. Тип не перемещаемый и не назначаемый. И в этом случае он тоже не копируется. Обычно предпочтительнее навязывать const
-ness с помощью интерфейса (в этом случае предоставляйте только cosnt
геттеры). Вы получаете тот же уровень защиты const
при сохранении семантики движений.
Простой обходной путь для обработки неудобных типов, таких как MyStruct
, заключается в том, чтобы обернуть их в std::unique_ptr
. std::map<std::string, std::unique_ptr<MyStruct>>
решит проблему за счет дополнительного распределения за экземпляр. В зависимости от варианта использования это может быть разумным компромиссом или нет. Но поскольку в примере динамически распределяются int
с уникальным владельцем, я предполагаю, что динамическое распределение не считается проблематичным.
@Scheff'sCat нравится это? myMap.emplace(piecewise_construct, "ab", std::forward_as_tuple(move(a), move(b)) );
тоже не получилось :)
Семантика перемещения не работает с постоянными данными.
И вам нужно перегрузить операторы вашей структуры. https://en.cppreference.com/w/cpp/language/rule_of_three
Но суть emplace в том, что вещь создается на месте, поэтому должен быть какой-то способ вставить элемент без перемещения или копирования, не так ли?
Во-первых, вы передаете первый аргумент не как std::string, а как const char[N]. Во-вторых, вы пытаетесь сконструировать объект в фигурных скобках без перегруженного оператора и получаете то, что получаете. В-третьих, вы должны узнать, как std::map работает с пользовательскими типами.
Оба
myMap.emplace(piecewise_construct,
forward_as_tuple("ab"),
forward_as_tuple(move(a), move(b))
);
и
myMap.try_emplace("ab", move(a), move(b));
должен работать в C++20. Вам нужно построить пару на месте, потому что она будет не копируемой и не перемещаемой из-за MyStruct
. Остается только семейство функций-членов emplace
. Использование списка инициализаторов в фигурных скобках в emplace
никогда не сработает, потому что emplace
только пересылает аргументы, но не может вывести типы для аргументов.
До C++20 эти функции-члены также не будут работать, поскольку внутри они используют прямую инициализацию с круглыми скобками. MyStruct
однако не имеет конструктора, соответствующего двум аргументам. В C++20 инициализаторы в скобках также могут выполнять агрегатную инициализацию, и это будет работать, поскольку MyStruct
является агрегатом.
Я не думаю, что есть какая-либо возможность добавить элемент на карту до C++20 без изменения MyStruct
или добавления для него неявного преобразования, потому что он может быть только инициализирован агрегатом, но должен быть emplace
сконструирован, что не t поддерживает агрегатную инициализацию.
Единственным исключением из этого должна быть инициализация по умолчанию. Например
myMap.emplace(piecewise_construct,
forward_as_tuple("ab"),
forward_as_tuple()
);
должен работать и до C++20, потому что MyStruct
конструируется по умолчанию.
Можно добавить неявное преобразование для выполнения агрегатной инициализации, например:
struct A {
std::unique_ptr<int> a;
std::unique_ptr<int> b;
operator MyStruct() && {
return {std::move(a), std::move(b)};
}
};
//...
myMap.emplace("ab", A{ std::move(a), std::move(b) });
Это, вероятно, будет работать в режиме С++ 17 на компиляторах, хотя технически это неправильно, поскольку обязательное удаление копии не применяется к инициализации оператором преобразования. См. СРГ 2327.
Что касается изменений MyStruct
: в частности, два приведенных выше примера также должны работать до C++20, если вы добавите соответствующий конструктор:
struct MyStruct {
MyStruct(std::unique_ptr<int> a_, std::unique_ptr<int> b_) : a(std::move(a_)), b(std::move(b_)) { }
const std::unique_ptr<int> a;
const std::unique_ptr<int> b;
};
Ах да, конструктор по умолчанию все еще работает. И тогда я могу (конечно, чисто теоретически, никто никогда не должен делать такие вещи) отбрасывать константу для присваивания чего-то указателям: P
@Mircode Нет, ты не можешь этого сделать. Это будет UB: тимсонг-cpp.github.io/cppwp/n4868/…
Я не знал об этом ... Это потому, что под ними есть разделы памяти только для чтения?
@Mircode Вероятно, предполагается, что компилятор может выполнять определенные оптимизации, предполагая, что значения никогда не меняются. Тем не менее, я думаю, что стандарт в настоящее время согласован только для const-complete объектов. Если только элемент const
, технически вероятны другие способы изменить значение, которые не запрещены связанным предложением.
К вашему сведению: std::piecewise_construct (Вы пробовали это?)