在 C++ 中实现 SFINAE 的方法
Approaches to function SFINAE in C++
我在一个项目中大量使用 SFINAE 函数,我不确定以下两种方法之间是否存在任何差异(除了样式):
#include <cstdlib>
#include <type_traits>
#include <iostream>
template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo()
{
std::cout << "method 1" << std::endl;
}
template <class T, std::enable_if_t<std::is_same_v<T, double>>* = 0>
void foo()
{
std::cout << "method 2" << std::endl;
}
int main()
{
foo<int>();
foo<double>();
std::cout << "Done...";
std::getchar();
return EXIT_SUCCESS;
}
程序输出符合预期:
method 1
method 2
Done...
我在Whosebug中看到方法2用的比较多,但是我更喜欢方法1
这两种方法有什么不同的情况吗?
I have seen method 2 used more often in Whosebug, but I prefer method 1.
建议:首选方法二
这两种方法都适用于单个函数。当您有多个具有相同签名的函数,并且您只想启用集合中的一个函数时,就会出现问题。
假设您想要启用 foo()
,版本 1,当 bar<T>()
(假装它是一个 constexpr
函数)是 true
,并且 foo()
,版本 2,当 bar<T>()
是 false
.
有
template <typename T, typename = std::enable_if_t<true == bar<T>()>>
void foo () // version 1
{ }
template <typename T, typename = std::enable_if_t<false == bar<T>()>>
void foo () // version 2
{ }
你得到一个编译错误,因为你有歧义:两个 foo()
具有相同签名的函数(默认模板参数不会更改签名)。
但是下面的解决方法
template <typename T, std::enable_if_t<true == bar<T>(), bool> = true>
void foo () // version 1
{ }
template <typename T, std::enable_if_t<false == bar<T>(), bool> = true>
void foo () // version 2
{ }
有效,因为 SFINAE 修改了函数的签名。
不相关的观察:还有第三种方法:enable/disable return 类型(显然 class/struct 构造函数除外)
template <typename T>
std::enable_if_t<true == bar<T>()> foo () // version 1
{ }
template <typename T>
std::enable_if_t<false == bar<T>()> foo () // version 2
{ }
与方法 2 一样,方法 3 与选择具有相同签名的替代函数兼容。
除了之外,更喜欢方法 2 的另一个原因是使用方法 1,您可以(不小心)将显式类型参数作为第二个模板参数传递,从而完全破坏 SFINAE 机制。这可能是拼写错误、copy/paste 错误,或者是对更大模板机制的疏忽。
#include <cstdlib>
#include <type_traits>
#include <iostream>
// NOTE: foo should only accept T=int
template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo(){
std::cout << "method 1" << std::endl;
}
int main(){
// works fine
foo<int>();
// ERROR: subsitution failure, as expected
// foo<double>();
// Oops! also works, even though T != int :(
foo<double, double>();
return 0;
}
我在一个项目中大量使用 SFINAE 函数,我不确定以下两种方法之间是否存在任何差异(除了样式):
#include <cstdlib>
#include <type_traits>
#include <iostream>
template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo()
{
std::cout << "method 1" << std::endl;
}
template <class T, std::enable_if_t<std::is_same_v<T, double>>* = 0>
void foo()
{
std::cout << "method 2" << std::endl;
}
int main()
{
foo<int>();
foo<double>();
std::cout << "Done...";
std::getchar();
return EXIT_SUCCESS;
}
程序输出符合预期:
method 1
method 2
Done...
我在Whosebug中看到方法2用的比较多,但是我更喜欢方法1
这两种方法有什么不同的情况吗?
I have seen method 2 used more often in Whosebug, but I prefer method 1.
建议:首选方法二
这两种方法都适用于单个函数。当您有多个具有相同签名的函数,并且您只想启用集合中的一个函数时,就会出现问题。
假设您想要启用 foo()
,版本 1,当 bar<T>()
(假装它是一个 constexpr
函数)是 true
,并且 foo()
,版本 2,当 bar<T>()
是 false
.
有
template <typename T, typename = std::enable_if_t<true == bar<T>()>>
void foo () // version 1
{ }
template <typename T, typename = std::enable_if_t<false == bar<T>()>>
void foo () // version 2
{ }
你得到一个编译错误,因为你有歧义:两个 foo()
具有相同签名的函数(默认模板参数不会更改签名)。
但是下面的解决方法
template <typename T, std::enable_if_t<true == bar<T>(), bool> = true>
void foo () // version 1
{ }
template <typename T, std::enable_if_t<false == bar<T>(), bool> = true>
void foo () // version 2
{ }
有效,因为 SFINAE 修改了函数的签名。
不相关的观察:还有第三种方法:enable/disable return 类型(显然 class/struct 构造函数除外)
template <typename T>
std::enable_if_t<true == bar<T>()> foo () // version 1
{ }
template <typename T>
std::enable_if_t<false == bar<T>()> foo () // version 2
{ }
与方法 2 一样,方法 3 与选择具有相同签名的替代函数兼容。
除了
#include <cstdlib>
#include <type_traits>
#include <iostream>
// NOTE: foo should only accept T=int
template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo(){
std::cout << "method 1" << std::endl;
}
int main(){
// works fine
foo<int>();
// ERROR: subsitution failure, as expected
// foo<double>();
// Oops! also works, even though T != int :(
foo<double, double>();
return 0;
}