模板参数与默认模板参数与 return 类型的函数模板参数推导
function template parameter deduction of template parameter vs of default template parameter vs of return type
这是一个关于当模板参数用作模板参数与默认模板参数与 return 类型时模板推导如何工作的问题。
1: 普通模板参数
经测试,使用 GCC 和 VS 编译以下代码片段无法推断出由 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: error C2783: 'void SwapInPlace(T &,T &)': could not deduce
template argument for '__formal'
GCC: couldn't deduce template parameter -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: return 类型
在 return 的情况下,键入所有内容都可以编译并运行良好:
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
的所有内容。归结为这三个:
void
是无效的匿名模板参数:
template<class T, void>
void SwapInPlace( T& left, T& right );
A void*
作为具有默认值的匿名模板参数是可以的:
template<class T, void* = nullptr >
void SwapInPlace( T& left, T& right );
void
因为 return 类型可以:
template<class T>
void SwapInPlace( T& left, T& right );
如果您将第一种情况更改为有效的匿名模板参数 而没有 默认值,例如 int
或 void*
,它将编译:
template<class T, int>
void SwapInPlace( T& left, T& right );
...直到您尝试实际使用它。然后你会得到“无法推断模板参数'<anonymous>
'”或类似的。
how deduction works in this 4 cases: why it fails in first case and succeds in three other?
第一个案例
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
你已经从 a
和 b
推导出 T
为 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
不是整数,您将丢失 return 值
template< class T >
SwapInPlace( T& left, T& right )
所以代码无效,所以该功能被禁用。
第四种情况:见第一种情况
这是一个关于当模板参数用作模板参数与默认模板参数与 return 类型时模板推导如何工作的问题。
1: 普通模板参数
经测试,使用 GCC 和 VS 编译以下代码片段无法推断出由 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: error C2783: 'void SwapInPlace(T &,T &)': could not deduce template argument for '__formal'
GCC: couldn't deduce template parameter -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: return 类型
在 return 的情况下,键入所有内容都可以编译并运行良好:
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
的所有内容。归结为这三个:
void
是无效的匿名模板参数:
template<class T, void>
void SwapInPlace( T& left, T& right );
A void*
作为具有默认值的匿名模板参数是可以的:
template<class T, void* = nullptr >
void SwapInPlace( T& left, T& right );
void
因为 return 类型可以:
template<class T>
void SwapInPlace( T& left, T& right );
如果您将第一种情况更改为有效的匿名模板参数 而没有 默认值,例如 int
或 void*
,它将编译:
template<class T, int>
void SwapInPlace( T& left, T& right );
...直到您尝试实际使用它。然后你会得到“无法推断模板参数'<anonymous>
'”或类似的。
how deduction works in this 4 cases: why it fails in first case and succeds in three other?
第一个案例
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
你已经从 a
和 b
推导出 T
为 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
不是整数,您将丢失 return 值
template< class T >
SwapInPlace( T& left, T& right )
所以代码无效,所以该功能被禁用。
第四种情况:见第一种情况