#c #c 11 #c 17 #variadic-templates #template-meta-programming
#c #c 11 #c 17 #variadic-шаблоны #шаблон-метапрограммирование
Вопрос:
Надеюсь, это заинтригует некоторых в сообществе. Надеюсь, это не слишком очевидно, потому что я не уверен, что происходит. Я создал класс шаблонов variadic с рекурсивным определением, в основном как интересный самостоятельный вызов. Вроде как кортеж, этот класс создает unordered_maps из unordered_maps произвольной глубины и с произвольными типами ключей на каждом уровне. Таким образом, вы могли бы, например, создать nested_map<int, std::string, float, int>
, а затем установить его с помощью map["fred"][3.4][42] = 35;
Вот кода — не слишком безумно.
template<typename T, typename K, typename ... KS> struct nested_map_base : std::unordered_map<K, T>
{
T amp;operator[](const K amp;key)
{
// just to verify we get to the bottom of things recursively
std::cout << "base: key = " << key << std::endl;
return this->std::unordered_map<K, T>::operator[](key);
}
};
template<typename T, typename New_K, typename K, typename ... KS>
struct nested_map_base<T, New_K, K, KS ...>
: std::unordered_map<New_K, nested_map_base<T, K, KS...>>
{
nested_map_base<T, K, KS...> amp;operator[](const New_K amp;new_key)
{
// just for debugging and to demonstrate that it's working
// for purposes of this question
std::cout << "midway: key = " << new_key << std::endl;
return this->std::unordered_map<New_K, nested_map_base<T, K, KS...>>::operator[](new_key);
}
};
Работает нормально. Запустив следующий код, получаем ожидаемый результат —
std::cout << "Method1:" << std::endl << std::endl;
nested_map_base<int, std::string, double, int> test_nest;
std::cout << "insert" << std::endl;
test_nest["leonard"][4.8][45] = 111;
std::cout << "retrieve" << std::endl;
int amp;answer = test_nest["leonard"][4.8][45];
std::cout << "Aanswer should be 111. Answer is " << answer << std::endl << std::endl;
создает —
Method1:
insert
midway: key = leonard
midway: key = 4.8
base: key = 45
retrieve
midway: key = leonard
midway: key = 4.8
base: key = 45
Aanswer should be 111. Answer is 111
Аккуратно. Затем я подумал, что хотел бы обернуть его во внешний класс, чтобы сохранить приватность реализации, поэтому я просто начал так —
template<typename datum_type, typename ... keys> class nested_map
{
private:
nested_map_base<datum_type, keys ...> backing_store;
public:
template<typename Base_key, typename ... KS> auto operator[](const Base_key amp;key)
{
return backing_store[key];
}
};
Там ничего особенного, и сначала казалось, что это работает, но следующий код выдает другие результаты —
std::cout << "Method2:" << std::endl << std::endl;
nested_map<int, std::string, double, int> test_nest;
std::cout << "insert" << std::endl;
test_nest["leonard"][4.8][45] = 111;
std::cout << "retrieve" << std::endl;
int amp;answer = test_nest["leonard"][4.8][45];
std::cout << "Answer should be 111. Answer is " << answer << std::endl << std::endl;
Это приводит к этому —
Method2:
insert
midway: key = leonard
midway: key = 4.8
base: key = 45
retrieve
midway: key = leonard
midway: key = 4.8
base: key = 45
Answer should be 111. Answer is 0
Метапрограммирование рекурсивных переменных шаблонов полно подводных камней, и есть причины, по которым вещи не очень часто переносятся, поэтому я не был шокирован тем, что обернутый не сработал, но что меня удивило, так это ТО, КАК это не сработало. Он повторился, как и ожидалось, вплоть до std::unordered_map
, который содержал тип данных терминала. В отладчике ссылка на int была восстановлена из карты терминала, и в простом тестовом коде ей было присвоено значение 111. Тот факт, что вы видите, что ключи повторяются во второй раз, указывает на то, что процесс извлечения, похоже, также работал, но ссылка была на значение int с нулевым значением. Любопытно.
Я копаю глубже в отладчике, чтобы увидеть, например, совпадает ли фактическое значение адреса установленной ссылки со ссылкой, используемой для извлечения. Я бы подумал, что единственный способ, которым они могли бы отличаться, — это если бы, например, предпоследний рекурсивный слой возвращал temp последнего слоя вместо ссылки на тот, который находится в структуре данных. Или, может быть, в обернутом случае все они временные вместо ссылок … что-то в этом роде, но перенос настолько легкий, что это кажется невозможным. Итак, я добавлю комментарии, если узнаю больше, но я подумал, что выложу это сообществу, чтобы посмотреть, есть ли что-то, что разные наборы глаз могут выявить при проверке.
Комментарии:
1.
auto
->auto amp;
.2. О … это была опечатка из более ранней реализации. Я удалил явный тип (который был ссылочным типом), заменил его на auto, но amp; остался. Хороший глазомер! И черт возьми! Это было быстро.
3. Ах … Я собирался удалить amp; и заметил, что его там не было. Вы имели в виду, что мне нужно вставить один. Таким образом, предполагается, что auto является типом без каких-либо ссылок? Это, безусловно, объясняет все это, приводя к передаче копий вместо ссылок.
4. Идеальный. Это было все. Синхронизация выходных данных. Спасибо. Приятная особенность падения в яму заключается в том, что в следующий раз легче вспомнить о проблеме.
Ответ №1:
На странице Cppreference, посвященной вычету аргумента шаблона, есть раздел о автоматических функциях возврата на странице Cppreference, в котором описываются правила, когда auto
используется возврат для функций.
Вывод аргумента шаблона используется в объявлениях функций, когда выводится значение
auto
спецификатора вreturn
типе функции изreturn
инструкции.Для функций с автоматическим возвратом параметр
P
получается следующим образом: вT
объявленном типе возвращаемой функции, которая включает auto , каждое вхождение auto заменяется параметром шаблона воображаемого типаU
. АргументA
является выражением инструкции return, и если у инструкции return нет операнда,A
являетсяvoid()
. После вычитанияU
изP
иA
следования правилам, описанным выше, выведенноеU
подставляется вT
, чтобы получить фактический возвращаемый тип.
Это объясняет, почему autoamp;
работает, а auto
нет.
Комментарии:
1. Я следую. Поскольку компилятор выводит
auto
из типа аргумента наreturn
, он не может знать, что то, что выглядит как возврат копии в стек, предназначено для возврата ссылки, как это было бы, если бы пользователь явно указал возвращаемый тип в качестве ссылки (как в случае развернутого рекурсивного определения). Что ж, мне всегда нравится, когда ответ на вопрос содержит важный совет по безопасности для будущих проектов.2. ДА. Вы поняли.