为什么不能为此嵌套模板化结构推断此函数的模板参数?

时间:2018-11-12 21:38:48

标签: c++ templates

我有这个嵌套的模板化结构:

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 :(得分:4)

因为标准是这样

标准之所以这样说,是因为反转任意编译时间图等效于暂停。

这等同于暂停,因为模板元编程已完成图灵化。

这是图灵完整的,因为实际上很难制作出不是图灵完整的有用的编程语言。它是偶然发生的,避免使用它会导致一种语言,它需要对看似简单的事物进行恼人的解决方法。

因此,通常很难确定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 );
 }

Live example

答案 1 :(得分:2)

模板参数推导无法推断出嵌套名称说明符中出现的任何内容。例如,不允许从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);

答案 2 :(得分:0)

在您的特定情况下,嵌套类型B唯一地附加到封闭类型A的特定专业领域。各种B与它们所包围的A之间存在一一对应的关系。因此,理论上应该可以从特定的A推论B的论点。

但是,在通常情况下,嵌套类型名称可能引用别名(例如typedef-name),而不是真正的新类型名称(如您的示例)。如果嵌套别名,则1:1对应关系不再成立,其他答案中的示例对此进行了证明。

语言规范无法区分这两种情况(真正的 new 嵌套类型与嵌套别名),因此它选择了“公共分母”方法,并且始终会处理封闭模板参数为非推断上下文