Почему параметры шаблона этой функции не могут быть выведены для этой вложенной шаблонной структуры? - PullRequest
0 голосов
/ 13 ноября 2018

У меня есть эта вложенная шаблонная структура:

template <typename... Ts>
struct A
{
  template <unsigned i>
  struct B
  {
    int b;
  };
};

Это прекрасно компилируется.

Затем я пытаюсь определить эту функцию.

template <unsigned i, typename... Ts>
void foo(A<Ts...>::B<i> var)
{
  std::cout << var.b << std::endl;
}

Почему-то яне совсем понимаю, это не скомпилируется.Мне пришлось изменить его следующим образом, чтобы он работал.

template <unsigned i, typename... Ts>
void foo(typename A<Ts...>::template B<i> var)
{
  std::cout << var.b << std::endl;
}

Но потом, когда я называю это так:

A<int, float>::B<0> var = {0};
foo(var);

В нем говорится, что параметр шаблона iне может быть вычтено.

Я могу заставить его работать, явно добавив аргументы шаблона в вызов функции:

A<int, float>::B<0> var = {0};
foo<0, int, float>(var);

Почему это так?Как сделать так, чтобы мне не приходилось указывать параметры шаблона при вызове функции?

Попробуйте код: https://repl.it/repls/LavenderDefiantOctagons

Ответы [ 3 ]

0 голосов
/ 13 ноября 2018

Потому что так говорится в стандарте.

Стандарт гласит, что инвертирование произвольных карт времени компиляции эквивалентно Halt.

Это эквивалентно Остановке, потому что метапрограммирование шаблона завершено по Тьюрингу.

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

Таким образом, в целом проблема определения A A<int, float>::B<0> от этого преобразования и его реверсии является сложной, поэтому в стандарте C ++ говорится, что компилятор не пытается.

Если вы напишите свое собственное отображение, вы можете сделать это. Во-первых, вы должны понимать, что у типов B нет "волос", поскольку в типе B нет ничего такого, что присоединяет его к типу A, который использовался для его создания. Мы можем добавить «волосы»:

template<class...Ts>
struct A {
  template<unsigned i>
  struct B {
    using daddy = A;
    int b;
  };
};

теперь мы можем найти A из типа B<?>.

Далее давайте добавим несколько обратных карт:

 template<class...Ts>
 struct types_t {using type=types_t;};

 template<class X>
 struct get_types;
 template<class X>
 using get_types_t=typename get_types<X>::type;
 template<template<class...>class Z, class...Ts>
 struct get_types<Z<Ts...>>:types_t<Ts...>{};
 template<class B>
 struct get_B_index;
 template<unsigned i, template<unsigned>class B>
 struct get_B_index<B<i>>:std::integral_constant<unsigned, i>{};

и из этого мы можем получить A аргументы шаблона из B:

 template<class...Ts, unsigned i>
 void foo( types_t<Ts...>, std::integral_constant<unsigned, i>, typename A<Ts...>::template B<i> var ) {
 }
 template<class B>
 void foo( B b ) {
   using types = get_types_t< typename B::daddy >;
   using index = get_B_index< B >;
   return foo( types{}, index{}, b );
 }

Живой пример

0 голосов
/ 13 ноября 2018

В вашем конкретном случае вложенный тип B однозначно привязан к конкретной специализации типа оболочки A. Между разными B с и их A с существует однозначное соответствие. Таким образом, теоретически должна быть возможность вывести аргументы A из конкретного B.

Однако в общем случае имя вложенного типа может ссылаться на псевдоним (например, typedef-name), а не на действительно новое имя типа (как в вашем примере). В случае вложенного псевдонима соответствие 1: 1 больше не выполняется, что демонстрируется примерами из других ответов.

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

0 голосов
/ 13 ноября 2018

Вывод аргумента шаблона не может вывести ничего, что появляется в спецификаторе вложенного имени .Например, он не разрешит вывод T из параметра типа typename Foo<T>::bar.Я считаю, что причина этого в том, что такой вывод вообще невозможен;всегда могут быть частичные специализации Foo, которые определяют bar как некоторый произвольно сложный typedef.

Обходной путь должен определить вложенный тип как необъявленный тип, а затем ввести его с помощью typedef,но используйте оригинальное неопубликованное имя для вычета:

template <unsigned i, typename... Ts>
struct A_B { int b; }

template <typename... Ts> 
struct A {
    template <unsigned i> using B = A_B<i, Ts...>;
};

template <unsigned i, typename... Ts>
void foo(A_B<i, Ts...> var);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...