SFINAE 不可解的超载
SFINAE not solvable overload
上下文
我想检查一个元素是否存在于容器中。
我想编写一个通用函数来利用容器的结构。
特别是,该函数应该为那些支持该方法的数据结构选择方法 count()
(例如,std::set
、std::unordered_set
、...)。
在 C++17 中我们可以这样写:
#include <algorithm>
#include <iterator>
template <typename Container, typename Element>
constexpr bool hasElement(const Container& c, const Element& e) {
if constexpr (hasCount<Container>::value) {
return c.count(e);
} else {
return std::find(std::cbegin(c), std::cend(c), e) != std::cend(c);
}
}
好的!现在我们需要实现 hasCount<T>
trait.
使用 SFINAE(以及 C++17 中的 std::void_t
)我们可以这样写:
#include <type_traits>
template <typename T, typename = std::void_t<>>
struct hasCount: std::false_type {};
template <typename T>
struct hasCount<T, std::void_t<decltype(&T::count)>> : std::true_type {};
这种方法效果很好。例如,以下代码片段可以编译(当然是使用之前的定义):
struct Foo {
int count();
};
struct Bar {};
static_assert(hasCount<Foo>::value);
static_assert(!hasCount<Bar>::value);
问题
当然,我打算在STL数据结构上使用hasCount
,比如std::vector
,std::set
。问题来了!
自 C++14 起,std::set<T>::count
具有模板重载。
因此static_assert(hasCount<std::set<int>>::value);
失败了!
那是因为decltype(&std::set<int>::count)
由于过载无法自动推导
问题
鉴于上下文:
- 有没有办法解决自动过载?
- 如果没有,是否有另一种方法可以写出更好的
hasCount
特征? (C++20 概念 不可用)。
应避免外部依赖项(库,例如 boost)。
从问题评论来看,正确的做法是检查“调用表达式”(而不是方法的存在)。
因此,trait结构体的改进可能如下:
#include <type_traits>
template <typename T, typename U, typename = std::void_t<>>
struct hasCount : std::false_type {};
template <typename T, typename U>
struct hasCount<T, U, std::void_t<decltype(std::declval<T>().count(std::declval<U>()))>> :
std::true_type {};
给定两个实例 t
和 u
类型分别为 T
和 U
,它检查表达式 t.count(u)
是否有效。
因此以下代码有效:
static_assert(hasCount<std::set<int>, int>::value);
解决问题中的问题
补充说明
通用算法现在可以通过以下方式实现:
#include <algorithm>
#include <iterator>
template <typename Container, typename Element>
constexpr bool hasElement(const Container& c, const Element& e) {
if constexpr (hasCount<Container, Element>::value) {
return c.count(e);
} else {
return std::find(std::cbegin(c), std::cend(c), e) != std::cend(c);
}
}
is there a way to solve the automatic overload?
是的,如果您知道要传递给该方法的参数的类型。在你的情况下,如果我理解正确的话,Element
.
您的回答显示了如何解决修改原始代码的问题。接下来我提出一个基于仅声明 constexpr
函数
的解决方案
is there another way to write a better hasCount trait?
我不知道是否更好,但通常我更喜欢使用 constexpr
函数。
如下(注意:代码未测试 tested 直接来自 OP)
template <typename...>
constexpr std::false_type hasCountF (...);
template <typename T, typename ... As>
constexpr auto hasCountF (int)
-> decltype( std::declval<T>().count(std::declval<As>()...), std::true_type{});
template <typename ... Ts>
using has_count = decltype(hasCountF<Ts...>(1));
也许还有(仅来自 C++14)
template <typename ... Ts>
constexpr auto has_count_v = has_count<Ts...>::value:
你可以这样调用它
if constexpr ( has_count_v<Container, Element> )
在你的例子中,在你的函数中使用 Container
c
和 Element
e
,你可以使它更简单(避免很多 std::declval()
's),您可以尝试使用以下几个函数
template <typename...>
constexpr std::false_type hasCountF (...);
template <typename C, typename ... As>
constexpr auto hasCountF (C const & c, As const & ... as)
-> decltype( c.count(as...), std::true_type{});
调用如下
if constexpr ( decltype(hasCountF(c, e))::value )
但我更喜欢前面的解决方案,因为需要更多的打字但更灵活。
在这样的地方,简单地推迟到具有后备的单独重载集会很有帮助:
template <typename Container, typename Element>
constexpr auto hasElement_impl(int, const Container& c, const Element& e)
-> decltype(c.count(e))
{
return c.count(e);
}
template <typename Container, typename Element>
constexpr bool hasElement_impl(long, const Container& c, const Element& e)
{
return std::find(c.begin(), c.end(), e) != c.end();
}
template <typename Container, typename Element>
constexpr bool hasElement(const Container& c, const Element& e)
{
return hasElement_impl(0, c, e);
}
如果你能做到 c.count(e)
,那就去做吧。否则,回退到 find()
。在这种情况下,您不需要编写类型特征,事实上,问题本身就说明了尝试走那条路的问题。不做就简单多了。
或者,使用 Boost.HOF:
constexpr inline auto hasElement = boost::hof::first_of(
[](auto const& cnt, auto const& elem) BOOST_HOF_RETURNS(cnt.count(elem)),
[](auto const& cnt, auto const& elem) {
return std::find(cnt.begin(), cnt.end(), elem) != cnt.end();
}
);
在 C++20 中,由于有了概念,这种算法的改进将变得容易得多:
template <AssociativeContainer C, typename E>
bool hasElement(const C& c, const E& e) { return c.count(e); }
template <typename C, typename E>
bool hasElement(const C& c, const E& e) { return std::find(c.begin(), c.end(), e) != c.end(); }
上下文
我想检查一个元素是否存在于容器中。
我想编写一个通用函数来利用容器的结构。
特别是,该函数应该为那些支持该方法的数据结构选择方法 count()
(例如,std::set
、std::unordered_set
、...)。
在 C++17 中我们可以这样写:
#include <algorithm>
#include <iterator>
template <typename Container, typename Element>
constexpr bool hasElement(const Container& c, const Element& e) {
if constexpr (hasCount<Container>::value) {
return c.count(e);
} else {
return std::find(std::cbegin(c), std::cend(c), e) != std::cend(c);
}
}
好的!现在我们需要实现 hasCount<T>
trait.
使用 SFINAE(以及 C++17 中的 std::void_t
)我们可以这样写:
#include <type_traits>
template <typename T, typename = std::void_t<>>
struct hasCount: std::false_type {};
template <typename T>
struct hasCount<T, std::void_t<decltype(&T::count)>> : std::true_type {};
这种方法效果很好。例如,以下代码片段可以编译(当然是使用之前的定义):
struct Foo {
int count();
};
struct Bar {};
static_assert(hasCount<Foo>::value);
static_assert(!hasCount<Bar>::value);
问题
当然,我打算在STL数据结构上使用hasCount
,比如std::vector
,std::set
。问题来了!
自 C++14 起,std::set<T>::count
具有模板重载。
因此static_assert(hasCount<std::set<int>>::value);
失败了!
那是因为decltype(&std::set<int>::count)
由于过载无法自动推导
问题
鉴于上下文:
- 有没有办法解决自动过载?
- 如果没有,是否有另一种方法可以写出更好的
hasCount
特征? (C++20 概念 不可用)。
应避免外部依赖项(库,例如 boost)。
从问题评论来看,正确的做法是检查“调用表达式”(而不是方法的存在)。
因此,trait结构体的改进可能如下:
#include <type_traits>
template <typename T, typename U, typename = std::void_t<>>
struct hasCount : std::false_type {};
template <typename T, typename U>
struct hasCount<T, U, std::void_t<decltype(std::declval<T>().count(std::declval<U>()))>> :
std::true_type {};
给定两个实例 t
和 u
类型分别为 T
和 U
,它检查表达式 t.count(u)
是否有效。
因此以下代码有效:
static_assert(hasCount<std::set<int>, int>::value);
解决问题中的问题
补充说明
通用算法现在可以通过以下方式实现:
#include <algorithm>
#include <iterator>
template <typename Container, typename Element>
constexpr bool hasElement(const Container& c, const Element& e) {
if constexpr (hasCount<Container, Element>::value) {
return c.count(e);
} else {
return std::find(std::cbegin(c), std::cend(c), e) != std::cend(c);
}
}
is there a way to solve the automatic overload?
是的,如果您知道要传递给该方法的参数的类型。在你的情况下,如果我理解正确的话,Element
.
您的回答显示了如何解决修改原始代码的问题。接下来我提出一个基于仅声明 constexpr
函数
is there another way to write a better hasCount trait?
我不知道是否更好,但通常我更喜欢使用 constexpr
函数。
如下(注意:代码未测试 tested 直接来自 OP)
template <typename...>
constexpr std::false_type hasCountF (...);
template <typename T, typename ... As>
constexpr auto hasCountF (int)
-> decltype( std::declval<T>().count(std::declval<As>()...), std::true_type{});
template <typename ... Ts>
using has_count = decltype(hasCountF<Ts...>(1));
也许还有(仅来自 C++14)
template <typename ... Ts>
constexpr auto has_count_v = has_count<Ts...>::value:
你可以这样调用它
if constexpr ( has_count_v<Container, Element> )
在你的例子中,在你的函数中使用 Container
c
和 Element
e
,你可以使它更简单(避免很多 std::declval()
's),您可以尝试使用以下几个函数
template <typename...>
constexpr std::false_type hasCountF (...);
template <typename C, typename ... As>
constexpr auto hasCountF (C const & c, As const & ... as)
-> decltype( c.count(as...), std::true_type{});
调用如下
if constexpr ( decltype(hasCountF(c, e))::value )
但我更喜欢前面的解决方案,因为需要更多的打字但更灵活。
在这样的地方,简单地推迟到具有后备的单独重载集会很有帮助:
template <typename Container, typename Element>
constexpr auto hasElement_impl(int, const Container& c, const Element& e)
-> decltype(c.count(e))
{
return c.count(e);
}
template <typename Container, typename Element>
constexpr bool hasElement_impl(long, const Container& c, const Element& e)
{
return std::find(c.begin(), c.end(), e) != c.end();
}
template <typename Container, typename Element>
constexpr bool hasElement(const Container& c, const Element& e)
{
return hasElement_impl(0, c, e);
}
如果你能做到 c.count(e)
,那就去做吧。否则,回退到 find()
。在这种情况下,您不需要编写类型特征,事实上,问题本身就说明了尝试走那条路的问题。不做就简单多了。
或者,使用 Boost.HOF:
constexpr inline auto hasElement = boost::hof::first_of(
[](auto const& cnt, auto const& elem) BOOST_HOF_RETURNS(cnt.count(elem)),
[](auto const& cnt, auto const& elem) {
return std::find(cnt.begin(), cnt.end(), elem) != cnt.end();
}
);
在 C++20 中,由于有了概念,这种算法的改进将变得容易得多:
template <AssociativeContainer C, typename E>
bool hasElement(const C& c, const E& e) { return c.count(e); }
template <typename C, typename E>
bool hasElement(const C& c, const E& e) { return std::find(c.begin(), c.end(), e) != c.end(); }