SFINAE 不可解的超载

SFINAE not solvable overload

上下文

我想检查一个元素是否存在于容器中。

我想编写一个通用函数来利用容器的结构。

特别是,该函数应该为那些支持该方法的数据结构选择方法 count()(例如,std::setstd::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::vectorstd::set。问题来了!

自 C++14 起,std::set<T>::count 具有模板重载。

因此static_assert(hasCount<std::set<int>>::value);失败了!

那是因为decltype(&std::set<int>::count)由于过载无法自动推导


问题

鉴于上下文:

应避免外部依赖项(库,例如 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 {};

给定两个实例 tu 类型分别为 TU,它检查表达式 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 cElement 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(); }