表达式 SFINAE:如何根据类型是否包含具有一个或多个参数的函数来选择模板版本
Expression SFINAE: how to select template version based on whether type contains a function with one or more arguments
我试图在编译时根据参数是否实现特定函数在不同的模板实现之间进行选择。这是一个常见问题(请参阅此 S.O. 问题和本文引用的此示例。常见的答案是"使用表达式SFINAE"。
大多数示例显示了如何使用表达式 SFINAE 根据零参数函数的存在进行选择。我试图通过使用declval
(松散地基于此示例(将这些调整到我的 1 参数用例中,但我似乎无法让它工作。
我确定我在下面的示例中做错了什么,但我无法弄清楚它是什么。该示例尝试定义模板的两个版本bool Util::Container::Contains(container, value)
这些版本将使用容器的内置find(value)
方法(如果存在(,否则将使用std::find(...)
回退到线性搜索
请注意:我知道我可以通过重载 Contains(( 来完成这项工作unordered_map、unordered_set等,但相反,我想弄清楚这种基于模式的方法,以便它会自动委托给任何容器的find(value)
而无需添加重载。
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <string>
namespace Util::Container {
namespace Detail
{
template <typename T>
class HasFindMethod
{
private:
typedef char YesType[1];
typedef char NoType[2];
// This is how the examples show it being done for a 0-arg function
//template <typename C> static YesType& Test(decltype(&C::find));
// Here's my attempt to make it match a 1-arg function
template <typename C> static YesType&
Test(decltype(std::declval<C>().find(std::declval<const C::value_type&>())));
template <typename C> static NoType& Test(...);
public:
enum { value = sizeof(Test<T>(0)) == sizeof(YesType) };
};
}
// Fallback: uses std::find() to do the lookup if no type-specific T::find(value) exists
template<typename T>
bool Contains(const T& in_container, const typename T::value_type& in_item)
{
const auto& result = std::find(in_container.cbegin(), in_container.cend(), in_item);
return (result != in_container.cend());
}
// Preferred: use T::find() to do the lookup if possible
template<typename T>
inline typename std::enable_if<Detail::HasFindMethod<T>::value, bool>::type
Contains(const T& in_container, const typename T::value_type& in_item)
{
return (in_container.find(in_item) != in_container.end());
}
}
int main()
{
const std::vector<int> v { 1, 2, 3 };
const std::unordered_map<int, std::string> m { {1,"1" }, {2,"2"} };
const std::unordered_set<std::string> s { "1" , "2" };
// These should use the std::find()-based version of Contains() since vector and unordered_map
// have no find(value_type) method. And they do.
const bool r_v = Util::Container::Contains(v, 2);
const bool r_m = Util::Container::Contains(m, { 2, "2" });
// !!!!!!
//
// This should use the T::find(value_type)-based version of Contains() since
// unordered_set has a find(value_type) method.
//
// But it doesn't --- that's the issue I'm trying to solve.
//
const bool r_s = Util::Container::Contains(s, "2");
}
如果有人能告诉我如何解决这个问题,我将不胜感激。
FWIW,我正在尝试在Visual Studio 2017 v15.8中实现这一点
decltype
的一个简单方法是
template<typename C, typename V>
auto Contains(const C& c, const V& value)
-> decltype(std::find(c.cbegin(), c.cend(), value) != c.cend())
{
return std::find(c.cbegin(), c.cend(), value) != c.cend();
}
template <typename C, typename Key>
auto Contains(const C& c, const Key& key)
-> decltype(c.find(key) != c.end())
{
return c.find(key) != c.end();
}
但是,当两个功能都可能时,您将有模棱两可的调用。
因此,只需添加额外的参数即可确定重载的优先级:
struct low_priority {};
struct high_priority : low_priority {};
template<typename C, typename V>
auto ContainsImpl(low_priority, const C& c, const V& value)
-> decltype(std::find(c.cbegin(), c.cend(), value) != c.cend())
{
return std::find(c.cbegin(), c.cend(), value) != c.cend();
}
template <typename C, typename Key>
auto ContainsImpl(high_priority, const C& c, const Key& key)
-> decltype(c.find(key) != c.end())
{
return c.find(key) != c.end();
}
template <typename C, typename T>
auto Contains(const C& c, const T& t)
-> decltype(ContainsImpl(high_priority{}, c, t))
{
return ContainsImpl(high_priority{}, c, t);
}
现在关于您的版本,您有几个问题
最后一个:
// Expected Fallback: uses std::find() to do the lookup if no type-specific T::find(value) exists
template<typename T>
bool Contains(const T&, const typename T::value_type&);
// Expected Preferred: use T::find() to do the lookup if possible
template<typename T>
typename std::enable_if<Detail::HasFindMethod<T>::value, bool>::type
Contains(const T&, const typename T::value_type&);
SFINAE允许丢弃过载,但不优先考虑它们。 您必须使用优先级,如上所示,或创建独占重载集:
template<typename T>
typename std::enable_if<!Detail::HasFindMethod<T>::value, bool>::type
Contains(const T&, const typename T::value_type&);
template<typename T>
typename std::enable_if<Detail::HasFindMethod<T>::value, bool>::type
Contains(const T&, const typename T::value_type&);
除此之外,正如评论中提到的map
家人将使用key_type
而不是value_type
。
那么你的检测代码有问题,
这就是示例显示它为 0-arg 函数完成的方式 模板静态 YesType& Test(decltype(&C::find((;
否,这会检测C
是否有方法find
(无重载(。
template <typename C> static YesType& Test(decltype(std::declval<C>().find(std::declval<const C::value_type&>())));
在这里,您使用SFINAE,但最终类型是(const_
(iterator
,并且Test<C>(0)
不会承受这种重载(除非迭代器可以从0
构建,这不是常规情况(。添加额外的*
是可能的,然后您在迭代器上具有指针,该指针可能由0
初始化。
否则,您可以使用提供的链接中提供的代码:
namespace detail{
template<class T, typename ... Args>
static auto test_find(int)
-> sfinae_true<decltype(std::declval<T>().find(std::declval<const Arg&>()...))>;
template<class, class ...>
static auto test_find(long) -> std::false_type;
} // detail::
template<class C, typename ... Args>
struct has_find : decltype(detail::test_find<T, Args...>(0)){};
// int has higher priority than long for overload resolution
然后将您的特征与std::enable_if
has_find<Container, Key>::value
一起使用.
直接的问题是你传递给Test
的参数与YesType
版本不兼容。
例如,Detail::HasFindMethod<std::unordered_set<int>>
将产生以下两个Test
签名(因为find
会返回iterator
(:
static YesType& Test(std::unordered_set<int>::iterator);
static NoType& Test(...);
您尝试使用参数0
调用Test
,该参数不可转换为iterator
。因此,选择了第二个。
作为解决方案,请使用指针:
template <typename C> static YesType&
Test(decltype(std::declval<C>().find(std::declval<const C::value_type&>()))*);
// ^
然后使用nullptr
参数进行检查:
enum { value = sizeof(Test<T>(nullptr)) == sizeof(YesType) };
现在我们会有歧义(Test(...)
也会匹配(,所以我们可以使那个匹配更差:
template <typename C, class ... Args> static NoType& Test(void*, Args...);
如其他答案所示,这仍然是一个相对复杂的解决方案(并且还有更多问题阻止它在您的实例中工作,例如当enable_if
确实工作时重载之间的歧义(。只是在这里解释您尝试中的特定塞子。
使用void_t实用程序可以实现更简单(在我看来(和更具可读性的解决方案:
template <typename T, typename Dummy = void>
struct has_member_find : std::false_type { };
template <typename T>
struct has_member_find<T,
std::void_t<decltype(std::declval<T>().find(std::declval<typename T::value_type &>()))>>
: std::true_type { };
template<typename T>
std::enable_if_t<!has_member_find<T>::value, bool>
Contains(const T& in_container, const typename T::value_type& in_item)
{
const auto& result = std::find(in_container.cbegin(), in_container.cend(), in_item);
return (result != in_container.cend());
}
template<typename T>
std::enable_if_t<has_member_find<T>::value, bool>
Contains(const T& in_container, const typename T::value_type& in_item)
{
return (in_container.find(in_item) != in_container.end());
}
请注意,void_t
仅在 C++17 起可用,但是如果您没有完整的 C++17 支持,您可以自己定义它,因为它的定义非常简单:
template< class... >
using void_t = void;
您可以在本文中了解有关此实用程序及其引入的模式的更多信息。
- static_assert在宏中,但也可以扩展到可以用作函数参数的东西
- C++中的高效循环缓冲区,它将被传递给C样式数组函数参数
- 当从函数参数中的临时值调用复制构造函数时
- 如何从"decltype()"获取函数参数的数量<funtion>?
- 如何将lambda作为模板类的成员函数参数
- 模板参数推导失败,函数参数/参数不匹配
- 如何在C++中将迭代器作为函数参数传递
- 将函数参数"const char*"转换为"std::string_view"是
- C++ 如何将数组值解压缩为函数参数
- 主函数参数的属性
- 具有两个间接寻址运算符 (C++) 的函数参数的用途
- "Warning: Comma within array index expression"但逗号分隔函数参数
- 如何定义在用作函数参数时工作的类模板的转换
- 将函数参数完美转发到函数指针:按值传递呢?
- 为什么我不能将引用作为 std::async 的函数参数传递
- 什么..(省略号)作为函数原型中唯一的函数参数,C++?
- 是否可以就地构造一个固定大小的数组作为函数参数?
- 接受模板作为函数参数
- 将成员函数作为构造函数参数调用时出错 "Variable is not a type name"
- Arduino 函数参数