c++ - 表达式 SFINAE:如何根据类型是否包含具有一个或多个参数的函数来选择模板版本
问题描述
我试图在编译时根据参数是否实现特定功能在不同模板实现之间进行选择。这是一个常见问题(请参阅this SO question和this example referenced by this article。常见答案是“使用表达式SFINAE”。
大多数示例显示了表达式 SFINAE 如何用于基于零参数函数的存在进行选择。declval
我试图通过使用(松散地基于此示例)使这些适应我的 1 参数用例,但我似乎无法让它工作。
我确定我在下面的示例中做错了什么,但我无法弄清楚它是什么。该示例试图定义一个模板的两个版本,如果它存在,bool Util::Container::Contains(container, value)
将使用容器的内置find(value)
方法,否则回退到使用线性搜索std::find(...)
请注意:我知道我可以通过为 unordered_map、unordered_set 等重载 Contains() 来完成这项工作,但我想找出这种基于模式的方法,以便它自动委托给任何容器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 函数完成的 //template static 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
.
推荐阅读
- sql - SQL - 其中 MIN 日期正好是 7 天前
- java - 在适用于 Linux 和 Mac 的 Windows 上使用 Java 15 创建个性化 JRE
- excel - Excel VBA - 自动填充表单
- typescript - 为什么 TS 会自动检测我的类声明而不是我的类型别名?
- ruby - 安装 Rspec 时遇到问题
- linux - VS 2019 Remote Linux 不会复制所有头文件
- r - 将 pivot_longer 与现有的 names_to 列一起使用
- angular - 为开发目的实时运行应用程序
- c# - c# 为什么我不能从最小化中返回我的应用程序?
- google-chrome - Chrome 开发工具 - 观看标签 - 正则表达式