Пытаясь предоставить решение для std::string_view и std::string в std::unordered_set, я играю с заменой std::unordered_set<std::string>
на std::unordered_map<std::string_view, std::unique_ptr<std::string>>
(значение равно std::unique_ptr<std::string>
, потому что оптимизация небольшой строки будет означать, что адрес базовых данных string
не всегда будет передаваться в результате std::move
.
Мой исходный тестовый код, который, похоже, работает (без заголовков):
using namespace std::literals;
int main(int argc, char **argv) {
std::unordered_map<std::string_view, std::unique_ptr<std::string>> mymap;
for (int i = 1; i < argc; ++i) {
auto to_insert = std::make_unique<std::string>(argv[i]);
mymap.try_emplace(*to_insert, std::move(to_insert));
}
for (auto&& entry : mymap) {
std::cout << entry.first << ": " << entry.second << std::endl;
}
std::cout << std::boolalpha << "\"this\" in map? " << (mymap.count("this") == 1) << std::endl;
std::cout << std::boolalpha << "\"this\"s in map? " << (mymap.count("this"s) == 1) << std::endl;
std::cout << std::boolalpha << "\"this\"sv in map? " << (mymap.count("this"sv) == 1) << std::endl;
return EXIT_SUCCESS;
}
Я компилирую с g++
7.2.0, строка компиляции g++ -O3 -std=c++17 -Wall -Wextra -Werror -flto -pedantic test_string_view.cpp -o test_string_view
не получает никаких предупреждений, затем запускаю, получая следующий вывод:
$ test_string_view this is a test this is a second test
second: second
test: test
a: a
this: this
is: is
"this" in map? true
"this"s in map? true
"this"sv in map? true
чего я и ожидал.
Меня больше всего беспокоит следующее:
mymap.try_emplace(*to_insert, std::move(to_insert));
имеет определенное поведение. *to_insert
полагается на то, что to_insert
не будет опустошен (путем создания std::unique_ptr
, хранящегося на карте), до тех пор, пока не будет построен string_view
. Два определения try_emplace
, которые будут рассмотрены:
try_emplace(const key_type& k, Args&&... args);
а также
try_emplace(key_type&& k, Args&&... args);
Я не уверен, что будет выбрано, но в любом случае кажется, что key_type
будет построено как часть вызова try_emplace
, в то время как аргументы для создания mapped_type
("значение", хотя карты, кажется, используют value_type
для ссылки на комбинированный ключ/значение pair
) пересылаются вместе, а не используются сразу, что делает код определенным. Я прав в этой интерпретации, или это неопределенное поведение?
Меня беспокоит то, что другие подобные конструкции, которые кажутся определенно неопределенными, все еще работают, например:
mymap.insert(std::make_pair<std::string_view,
std::unique_ptr<std::string>>(*to_insert,
std::move(to_insert)));
производит ожидаемый результат, в то время как аналогичные конструкции, такие как:
mymap.insert(std::make_pair(std::string_view(*to_insert),
std::unique_ptr<std::string>(std::move(to_insert))));
запускать Segmentation fault
во время выполнения, несмотря на то, что ни одна из них не вызывает никаких предупреждений, и обе конструкции, по-видимому, одинаково неупорядочены (неупорядоченные неявные преобразования в рабочем insert
, непоследовательное явное преобразование в segfaulting insert
), поэтому я не хочу говорить "try_emplace
работал на меня, так что все в порядке».
Обратите внимание, что хотя этот вопрос похож на C++11: вызов std::move() для списка аргументов, он не совсем дубликат (это то, что предположительно делает std::make_pair
здесь небезопасным, но не обязательно применимо к поведению, основанному на пересылке try_emplace
); в этом вопросе функция, получающая аргументы, получает std::unique_ptr
, немедленно запуская построение, в то время как try_emplace
получает аргументы для пересылки, а не std::unique_ptr
, поэтому, хотя std::move
"произошло" (но еще ничего не сделало), я думаю мы в безопасности, так как std::unique_ptr
построен "позже".