Обтекание рекурсивного шаблона класса Variadic меняет поведение.Зачем? - PullRequest
4 голосов
/ 08 апреля 2019

Надеюсь, это заинтригует некоторых в сообществе.Надеюсь, это не слишком очевидно, потому что я не уверен, что происходит.Я создал шаблон класса variadic с рекурсивным определением, в основном как интересная задача для себя.Вроде как кортеж, этот класс создает unordered_maps of 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 &operator[](const K &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...> &operator[](const New_K &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 &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

Neat.Тогда я подумал, что хотел бы обернуть его во внешний класс, чтобы сохранить приватность реализации, поэтому я просто начал так:Следующий код дает разные результаты -

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 &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.Любопытно.

Я копаю глубже в отладчике, чтобы увидеть, например, фактическое значение адреса установленной ссылки совпадает с той ссылкой, которая использовалась для получения.Единственный способ, которым они могут отличаться, я думаю, если бы, например, предпоследний рекурсивный слой возвращал временную температуру конечного слоя вместо ссылки на тот, что в структуре данных.Или, может быть, в завернутом случае они все временные, вместо ссылок ... что-то в этом роде, но если упаковка такая легкая, это кажется невозможным.Поэтому я добавлю комментарии, если узнаю больше, но я решил высказать это сообществу, чтобы узнать, есть ли что-то, что могут различить различные группы глаз при проверке.

1 Ответ

2 голосов
/ 08 апреля 2019

В есть раздел о функциях автоматического возврата на странице Cppreference , в котором описаны правила использования auto в качестве возврата для функций.

Вывод аргумента шаблона используется в объявлениях функций при выводе значения спецификатора auto в типе функции return из оператора return.

Для автоВозвращая функции, параметр P получается следующим образом: в T, объявленном типе возврата функции, включающей auto, каждое вхождение auto заменяется параметром шаблона воображаемого типа U.Аргумент A является выражением оператора возврата, и если оператор возврата не имеет операнда, A равен void().После вычитания U из P и A в соответствии с правилами, описанными выше, выведенное U подставляется в T для получения фактического возвращаемого типа.

Это объясняетпочему auto& работает, а auto - нет.

...