Вывод аргумента шаблона из тела функции - PullRequest
3 голосов
/ 26 января 2011

Если у нас есть этот шаблон функции,

template<typename T>
void f(T param) {}

Тогда мы можем вызвать его следующими способами:

int i=0;
f<int>(i);//T=int : no need to deduce T
f(i); //T=int : deduced T from the function argument!

//likewise
sample s;
f(s); //T=sample : deduced T from the function argument!

Теперь рассмотрим этот вариант шаблона функции выше,

template<typename TArg, typename TBody>
void g(TArg param) 
{
   TBody v=param.member;
}

Теперь, может ли компилятор выводить аргументы шаблона, если мы напишем,

sample s;
g(s); //TArg=sample, TBody=int??

Предположим, sample определен как,

struct sample
{
   int member;
};

Есть два основных вопроса:

  • Может ли компилятор определить аргументы шаблона во втором примере?
  • Если нет, то почему?Есть ли трудности?Если в стандарте ничего не говорится о "выводе аргумента шаблона из тела функции" , то это потому, что аргумент (ы) не может быть выведен?Или он не учитывал такой вывод, чтобы избежать усложнения языка?Или что?

Хотелось бы узнать ваши взгляды на такой вывод.


РЕДАКТИРОВАТЬ:

Кстати, GCC умеет выводить функциюаргументы, если мы напишем этот код:

template<typename T>
void h(T p)
{
        cout << "g() " << p << endl;
        return;
}
template<typename T>
void g(T p)
{
        h(p.member); //if here GCC can deduce T for h(), then why not TBody in the previous example?
        return;
}

Рабочая демонстрация для этого примера: http://www.ideone.com/cvXEA

Не рабочая демонстрация для предыдущего примера: http://www.ideone.com/UX038

Ответы [ 4 ]

5 голосов
/ 26 января 2011

Вы, вероятно, уже пришли к выводу, что компилятор не выведет TBody, изучив тип sample.member. Это добавило бы еще один уровень сложности к алгоритму вывода шаблона.

Алгоритм сопоставления шаблонов учитывает только функции подписи , а не их тела. Хотя это и используется не слишком часто, вполне законно просто объявить шаблонную функцию без предоставления тела:

template <typename T> void f(T param);

Это удовлетворяет компилятору. Чтобы удовлетворить компоновщик, вы, конечно, должны также где-то определить тело функции и убедиться, что все необходимые экземпляры были предоставлены. Но тело функции не обязательно должно быть видимым для клиентского кода функции шаблона, если требуемые экземпляры доступны во время ссылки. Тело должно было бы явно создать экземпляр функции, например:

template <> void f(int param);

Но это только частично относится к вашим вопросам, потому что вы можете представить себе сценарий, подобный следующему, где 2-й параметр может быть выведен из предоставленного параметра по умолчанию и который не будет компилироваться:

template<typename TArg, typename TBody>
void g(TArg param, TBody body = param.member);  // won't deduce TBody from TArg

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

Кроме того, это не требуется, поскольку существуют другие способы достижения того же намерения, как показано в примере ниже.

Ничто не мешает вам писать:

struct sample
{
   typedef int MemberType;
   MemberType member;
};

template<typename TArg>
void g(TArg param) 
{
   typename TArg::MemberType v = param.member;
}

sample s = { 0 };
g(s);

для получения того же эффекта.


Относительно вашего сэмпла, который вы добавили после редактирования: хотя кажется, что h(p.member) зависит от члена структуры, и, следовательно, алгоритм сопоставления с шаблоном должен дать сбой, это не так, потому что вы сделали его двухэтапным процессом:

  1. Увидев g(s);, компилятор ищет любую функцию, принимающую аргумент типа sample (с шаблоном или без!). В вашем случае наилучшее совпадение - void g(T p). На данный момент компилятор даже не посмотрел на тело g(T p)! .
  2. Теперь компилятор создает экземпляр g(T p), специализированный для T: sample. Поэтому, когда он видит h(p.member), он знает, что p.member имеет тип int, и попытается найти функцию h(), принимающую аргумент типа int. Ваша функция шаблона h(T p) оказывается наилучшим соответствием.

Обратите внимание, что если вы написали (обратите внимание на NOT_A_member ):

template<typename T>
void g(T p)
{
        h(p.NOT_A_member);
        return;
}

тогда компилятор все равно будет считать g() действительным соответствием на этапе 1. Вы получите ошибку, когда выяснится, что sample не имеет члена с именем NOT_A_member.

0 голосов
/ 26 января 2011

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

Для дополнительных кредитов, даже если бы компилятор посмотрел определение функции, код TBody v = parameter.member сам по себе не выводим, поскольку существуют потенциально бесконечные типы данных, которые могут принимать parameter.member в конструкторе.

Теперь о втором блоке кода. Чтобы понять это, весь процесс компиляции шаблона начинается, когда компилятор видит вызов функции g(x) в этой точке вызова. Компилятор видит, что лучшим кандидатом является функция шаблона template <typename T> void g( T ), и определяет, что тип T является частью разрешения перегрузки. Как только компилятор определяет, что это вызов шаблона, он выполняет первый проход компиляции для этой функции.

Во время первого прохода синтаксические проверки выполняются без фактической замены типа, поэтому аргумент шаблона T по-прежнему any-type , а аргумент p пока неизвестен тип. Во время первого прохода код проверяется, но зависимые имена пропускаются, и их значение просто предполагается. Когда компилятор видит p.member, а p имеет тип T, который является аргументом шаблона, он предполагает, что он будет членом еще неизвестного типа (это причина, почему, если это будет тип, у вас будет чтобы квалифицировать это здесь с typename). Вызов h(p.member); также зависит от аргумента типа T и остается без изменений, при условии, что после замены типа все будет иметь смысл.

Тогда компилятор действительно подставляет тип. На этом шаге T больше не является универсальным типом, а представляет конкретный тип sample. Теперь компилятор во время второго прохода пытается заполнить пробелы, оставшиеся во время первого прохода. Когда он видит p.member, он смотрит member внутри типа и определяет, что это int, и пытается разрешить вызов h( p.member ); с этим знанием. Поскольку тип T был разрешен перед этим вторым этапом, это эквивалентно внешнему вызову g(x): все типы известны, и компилятору нужно только решить, что является лучшей перегрузкой для вызова функции h, который принимает аргумент типа int&, и весь процесс начинается снова, шаблон h считается лучшим кандидатом, и ...

Для метапрограммирования действительно важно понимать, что вывод типа выполняется только для действительной сигнатуры функции, а не тела, и это делает его нетривиальным для начинающих. Использование enable_if (от boost или в другом месте) в сигнатуре функции в качестве аргумента или возвращаемого типа не является совпадением, но единственный способ заставить компилятор не заменить тип до , чтобы шаблон был выбран как лучший кандидат и ошибка замены превращается в фактическую ошибку (а не SFINAE)

0 голосов
/ 26 января 2011

TBody может быть неоднозначным, потому что sample может быть не единственным типом, имеющим член member. Кроме того, если g вызывает другие функции шаблона, компилятор не может узнать, какие другие ограничения могут быть наложены на TBody.

Так что в некоторых крайних случаях теоретически возможно вывести правильный тип для TBody, обычно это не так.

0 голосов
/ 26 января 2011

Ни один компилятор не может реализовать эту функцию согласованным образом. Вы просто слишком много просите.

...