Странное поведение при рекурсивном построении возвращаемого типа для функций с переменными параметрами - PullRequest
23 голосов
/ 19 апреля 2010

Вероятно, это будет очень простое объяснение, но я собираюсь рассказать как можно больше предыстории на случай, если я ошибаюсь. Дополнительные извинения за то, что так многословен. Я использую gcc4.5, и я понимаю, что поддержка c ++ 0x все еще несколько экспериментальна, но я собираюсь исходить из предположения, что есть причина, не связанная с ошибкой, для поведения, которое я вижу.

Я экспериментирую с шаблонами переменных функций. Конечная цель состояла в том, чтобы создать список противников из std::pair. Это не был пользовательский тип, просто строка из пары объектов. Функция, которая создает список, должна быть в некотором роде рекурсивной, а конечное возвращаемое значение зависит от результата рекурсивных вызовов. В качестве дополнительного поворота, последовательные параметры добавляются вместе перед вставкой в ​​список. Поэтому, если я пройду [1, 2, 3, 4, 5, 6], конечный результат должен быть {1 + 2, {3 + 4, 5 + 6}}.

Моя первая попытка была довольно наивной. Функция Build с двумя перегрузками. Один взял два одинаковых параметра и просто вернул их сумму. Другой взял два параметра и пакет параметров. Возвращаемым значением была пара, состоящая из суммы двух установленных параметров и рекурсивного вызова. В ретроспективе это была явно ошибочная стратегия, потому что функция не объявляется, когда я пытаюсь выяснить ее тип возвращаемого значения, поэтому у нее нет другого выбора, кроме как перейти к нерекурсивной версии.

Это я понимаю. Я запутался во второй итерации. Я решил сделать эти функции статическими членами шаблонного класса. Сами вызовы функций не параметризованы, а весь класс. Я предполагал, что когда рекурсивная функция попытается сгенерировать возвращаемый тип, она создаст целую новую версию структуры со своей собственной статической функцией, и все сработает само.

В результате получилось: «ошибка: нет подходящей функции для вызова BuildStruct<double, double, char, char>::Go(const char&, const char&)»

Код обидчика:

static auto Go(const Type& t0, const Type& t1, const Types&... rest)
    -> std::pair<Type, decltype(BuildStruct<Types...>::Go(rest...))>

Моя путаница возникает из-за того, что параметры BuildStruct всегда должны быть того же типа, что и аргументы, отправляемые BuildStruct::Go, но в коде ошибки Go отсутствуют первые два двойных параметра. Что мне здесь не хватает? Если мое первоначальное предположение о том, как будут выбираться статические функции, было неверным, почему оно пытается вызвать неправильную функцию, а не просто не находит функцию вообще? Кажется, что это просто смешивание типов, и я просто не могу придумать объяснение, почему. Если я добавляю дополнительные параметры к первоначальному вызову, он всегда возвращается к последнему шагу, прежде чем завершится сбоем, поэтому, возможно, сама рекурсия хотя бы частично работает. Это прямо противоположно первоначальной попытке, которая не всегда сразу находила вызов функции.

В конце концов, я преодолел проблему с довольно элегантным решением, которое вряд ли напоминает ни одну из первых двух попыток. Так что я знаю, как делать то, что хочу. Я ищу объяснение неудачи, которую я видел.

Полный код для подражания, так как я уверен, что моего словесного описания было недостаточно. Сначала несколько шаблонов, если вы чувствуете необходимость выполнить код и убедиться в этом сами. Затем первая попытка, которая закончилась неудачей, затем вторая попытка, которая не удалась.

#include <iostream>
using std::cout;
using std::endl;

#include <utility>

template<typename T1, typename T2>
std::ostream& operator <<(std::ostream& str, const std::pair<T1, T2>& p) {
  return str << "[" << p.first << ", " << p.second << "]";
}

//Insert code here    

int main() {
  Execute(5, 6, 4.3, 2.2, 'c', 'd');
  Execute(5, 6, 4.3, 2.2);
  Execute(5, 6);

  return 0;
}

Неструктурное решение:

template<typename Type>
Type BuildFunction(const Type& t0, const Type& t1) {
  return t0 + t1;
}

template<typename Type, typename... Rest>
auto BuildFunction(const Type& t0, const Type& t1, const Rest&... rest)
      -> std::pair<Type, decltype(BuildFunction(rest...))> {
  return std::pair<Type, decltype(BuildFunction(rest...))>
                  (t0 + t1, BuildFunction(rest...));
}

template<typename... Types>
void Execute(const Types&... t) {
  cout << BuildFunction(t...) << endl;
}

Результирующие ошибки:

test.cpp: In function 'void Execute(const Types& ...) [with Types = {int, int, double, double, char, char}]':
test.cpp:33:35:   instantiated from here
test.cpp:28:3: error: no matching function for call to 'BuildFunction(const int&, const int&, const double&, const double&, const char&, const char&)'

Структурное решение:

template<typename... Types>
struct BuildStruct;

template<typename Type>
struct BuildStruct<Type, Type> {
  static Type Go(const Type& t0, const Type& t1) { return t0 + t1; }
};

template<typename Type, typename... Types>
struct BuildStruct<Type, Type, Types...> {
  static auto Go(const Type& t0, const Type& t1, const Types&... rest)
        -> std::pair<Type, decltype(BuildStruct<Types...>::Go(rest...))> {
    return std::pair<Type, decltype(BuildStruct<Types...>::Go(rest...))>
               (t0 + t1, BuildStruct<Types...>::Go(rest...));
  }
};

template<typename... Types>
void Execute(const Types&... t) {
  cout << BuildStruct<Types...>::Go(t...) << endl;
}

Результирующие ошибки:

test.cpp: In instantiation of 'BuildStruct<int, int, double, double, char, char>':
test.cpp:33:3:   instantiated from 'void Execute(const Types& ...) [with Types = {int, int, double, double, char, char}]'
test.cpp:38:41:   instantiated from here
test.cpp:24:15: error: no matching function for call to 'BuildStruct<double, double, char, char>::Go(const char&, const char&)'
test.cpp:24:15: note: candidate is: static std::pair<Type, decltype (BuildStruct<Types ...>::Go(BuildStruct<Type, Type, Types ...>::Go::rest ...))> BuildStruct<Type, Type, Types ...>::Go(const Type&, const Type&, const Types& ...) [with Type = double, Types = {char, char}, decltype (BuildStruct<Types ...>::Go(BuildStruct<Type, Type, Types ...>::Go::rest ...)) = char]
test.cpp: In function 'void Execute(const Types& ...) [with Types = {int, int, double, double, char, char}]':
test.cpp:38:41:   instantiated from here
test.cpp:33:3: error: 'Go' is not a member of 'BuildStruct<int, int, double, double, char, char>'

1 Ответ

2 голосов
/ 16 июня 2010

Читая комментарии, кажется достаточно ясным, что это очень локализованная ошибка в конкретной версии G ++, и это единственный ответ, который когда-либо будет.

...