【问题标题】:function template parameter deduction of template parameter vs of default template parameter vs of return type函数模板参数推导模板参数vs默认模板参数vs返回类型
【发布时间】:2021-05-23 04:58:12
【问题描述】:

这是一个关于模板参数用作模板参数与用作默认模板参数与返回类型时模板推导如何工作的问题。

1:普通模板参数

经测试,使用 GCC 和 VS 编译以下 sn-p 无法推断出 std::enable_if_t 定义的模板参数

#include <iostream>
#include <type_traits>

template< class T, std::enable_if_t< std::is_integral_v< T > > >
void SwapInPlace( T& left, T& right )
{
   left = left ^ right;
   right = left ^ right;
   left = left ^ right;
} 

template< class T, std::enable_if_t< std::is_floating_point_v< T > > >
void SwapInPlace( T& left, T& right )
{
    left = left - right;
    right = left + right;
    left = right - left;
}

int main()
{   
   int i1 = 10;
   int i2 = -120;
   std::cout << i1 << " " << i2 << std::endl;
   SwapInPlace( i1, i2 );
   std::cout << i1 << " " << i2 << std::endl;

   double d1 = 1.1234;
   double d2 = 2.5678;  
   std::cout << d1 << " " << d2 << std::endl;
   SwapInPlace( d1 , d2 );
   std::cout << d1 << " " << d2 << std::endl;

   return 0;
}

VS: 错误 C2783: 'void SwapInPlace(T &,T &)': 无法推断 '__formal' 的模板参数

GCC:无法推断模板参数-anonymous-

2:默认模板参数

将第二个模板参数声明为默认模板参数使得推导工作正常:

template< class T, class = std::enable_if_t< std::is_integral_v< T > > >
void SwapInPlace( T& left, T& right )
{...}

template< class T, class = std::enable_if_t< std::is_floating_point_v< T > >, bool = true >
void SwapInPlace( T& left, T& right )
{...}

将bool = true 添加到第二次重载只是为了避免编译错误,因为无法根据默认模板参数重载函数模板。只想专注于扣除在这里可以正常工作的事实。如果只有一个模板使用默认参数,例如对于std::is_integral,只要我们将正确的参数传递给它,它就会编译并正常工作。

3:返回类型

在返回类型的情况下,一切都可以编译并且运行良好:

template< class T >
std::enable_if_t < std::is_integral_v< T > >
SwapInPlace( T& left, T& right )
{...}

template< class T >
std::enable_if_t < std::is_floating_point_v< T > >
SwapInPlace( T& left, T& right )
{...}

4:带默认值的模板参数

另一种编译此代码的方法是为std::enable_if 定义的模板参数添加默认值。在最后一个右尖括号之前添加了* = nullptr,因此如果std::enable_if 条件评估为true,那么我们的第二个参数变为void*,默认值为nullptr:

template< class T, std::enable_if_t< std::is_integral_v< T > >* = nullptr >
void SwapInPlace( T& left, T& right )
{...}

template< class T, std::enable_if_t< std::is_floating_point_v< T > >* = nullptr >
void SwapInPlace( T& left, T& right )
{...}

所以问题是:在这 4 种情况下如何演绎:为什么它在第一种情况下失败而在其他三种情况下成功?

【问题讨论】:

  • 它与enable_if 没有任何关系,因此您可以大大简化问题。你想知道为什么template&lt;class T, void&gt; 不起作用,但为什么template&lt;class T, void* = nullptr&gt; 起作用,对吧?
  • @TedLyngmo 不完全是,我想知道为什么它有效,为什么它不适用于我列出的案例。我最好奇为什么这适用于使用默认模板参数的情况。但我仍然想了解所有其他情况。
  • 是的,我说你得到完全相同的没有混入enable_if。 example
  • @TedLyngmo 当然。那我需要重写问题。

标签: c++ sfinae template-argument-deduction enable-if function-templates


【解决方案1】:

在这 4 种情况下如何演绎:为什么它在第一种情况下失败而在其他三种情况下成功?


第一种情况

template< class T, std::enable_if_t< std::is_integral_v< T > > >
void SwapInPlace( T& left, T& right )

假设std::is_integral_v 为真;使用 std::enable_if_t 替换你得到

template< class T, void>
void SwapInPlace( T& left, T& right )

这不是有效的 C++ 代码,因为它请求了一个 void 值(用于第二个模板参数),但 void 不能有一个有效值。

假设您用int(一种接受有效值的类型)替换第二个模板参数的类型

// .........................................................VVVVVV
template< class T, std::enable_if_t< std::is_integral_v< T >, int > >
void SwapInPlace( T& left, T& right )

如果你得到整数T 值

template <class T, int>
void SwapInPlace( T& left, T& right )

这是有效的,但是......假设调用是

int a{1}, b{2};

SwapInPlace(a, b); // compilation error

您知道T 是从a 和b 推导出来的int,但编译器无法确定第二个(未命名的)模板参数的值。

因此,要进行有效调用,您必须显式调用函数的第二个模板参数

SwapInPlace<int, 0>(a, b); // OK

这样调用是有效的,因为没有进行任何推断,并明确指出 T 是 int 并且 int 参数是 0。

这行得通,但不舒服,因为您必须明确可以推断出的T。

为避免此问题,您可以为第二个模板参数添加默认值

// .........................................................VVVVVV.VVVV
template< class T, std::enable_if_t< std::is_integral_v< T >, int > = 0 >
void SwapInPlace( T& left, T& right )

所以,如果是T 积分,你会得到

// ....................VVVV
template <class T, int = 0>
void SwapInPlace( T& left, T& right )

现在是简单的调用

int a{1}, b{2};

SwapInPlace(a, b); // OK now

因为T 被推导出为int 并且第二个模板参数默认为零。

有效,但您可以观察到您现在处于第四种情况(使用int 而不是void * 和0 而不是nullptr)


第二种情况

第二种情况是个坏主意。

template< class T, class = std::enable_if_t< std::is_integral_v< T > > >
void SwapInPlace( T& left, T& right )
{...}

当T是整数时

template< class T, class = void>
void SwapInPlace( T& left, T& right )
{...}

这是一个有效的代码,T 可以从 left 和 right 参数推导出来,第二个未命名的参数是默认的,因此不需要显式。

但是当T 不是整数时,SFINAE 失败只会丢弃第二个模板参数的默认值,所以你得到

template< class T, class>
void SwapInPlace( T& left, T& right )
{...}

并且代码是有效的,但是第二个模板参数不是默认的,所以必须是显式的。

所以,几乎和第一种情况一样,你有

float a{1.0f}, b{2.0f};

SwapInPlace(a, b); // compilation error
SwapInPlace<float, void>(a, b); // compile

不好的部分是默认值不能区分函数签名;所以如果你有两个SwapInPlace() 替代函数

template< class T, class = std::enable_if_t< std::is_integral_v< T > > >
void SwapInPlace( T& left, T& right )
{...}

template< class T, class = std::enable_if_t< not std::is_integral_v< T > > >
void SwapInPlace( T& left, T& right )
{...}

在你有替换之后

template< class T, class = void>
void SwapInPlace( T& left, T& right )
{...}

template< class T, class>
void SwapInPlace( T& left, T& right )
{...}

所以你有两个签名完全相同的函数(请记住:= void 不计算在内)。这对于 C++ 规则是不可接受的,因此会出现编译错误。

建议:避免第二种方式,因为当你必须开发替代功能时不起作用。


第三种情况

template< class T >
std::enable_if_t < std::is_integral_v< T > > SwapInPlace( T& left, T& right )
 {...}

据我所知,这是最简单的情况。

如果T 是整数,则得到

template< class T >
void SwapInPlace( T& left, T& right )

这是有效的代码,所以该功能被启用。

如果T 不是整数,则丢失返回值

template< class T >
     SwapInPlace( T& left, T& right )

所以代码无效,所以功能被禁用。


第四种情况:见第一种情况

【讨论】:

  • 感谢您的详细信息。关于在T 不是完整的情况下删除默认模板参数的情况-您在So, as in first case, you have 之后作为示例提供的代码将在这两种情况下编译-扣除参数和调用中提供的参数-正如您在那里使用整数。似乎您想改用double,但它在这两种情况下都不起作用-在调用中提供了扣除的参数和参数。无论如何,这部分有一些歧义,你能看看吗?
  • @SoupEndless - 好点。是的:在这种情况下,示例需要非整数类型的值。谢谢;更正(我希望)。
【解决方案2】:

您可以删除问题中有关 enable_if 的所有内容。归结为这三个:

void 是无效的匿名模板参数:

template<class T, void>
void SwapInPlace( T& left, T& right );

void* 作为匿名模板参数,默认值即可:

template<class T, void* = nullptr >
void SwapInPlace( T& left, T& right );

void 作为返回类型没问题:

template<class T>
void SwapInPlace( T& left, T& right );

如果您将第一个案例更改为有效的匿名模板参数没有默认值,如int 或void*,它将编译:

template<class T, int>
void SwapInPlace( T& left, T& right );

...直到您尝试实际使用它。然后你会得到“couldn't deuce template parameter '&lt;anonymous&gt;'”或类似的信息。

【讨论】:

  • 感谢您的简洁回答,问题正是关于匿名void 模板参数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-02
  • 1970-01-01
  • 2021-11-01
  • 2016-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多