SFINAE无法解决的过载

时间:2018-11-09 11:22:20

标签: c++ c++17 template-meta-programming sfinae typetraits

上下文

我要检查容器内是否存在元素。

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

特别是,该函数应该为支持该数据结构的数据结构选择方法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);

问题

当然,我将在{em> STL 数据结构上使用hasCount,例如std::vectorstd::set。这里有问题!

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

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

这是因为decltype(&std::set<int>::count)由于过载而无法自动推论。


问题

给出上下文:

  • 有没有办法解决自动超载?
  • 如果不是,是否还有另一种方法来编写更好的hasCount特征? ( C ++ 20概念不可用)。

应避免外部依赖项(库,例如 boost )。

3 个答案:

答案 0 :(得分:2)

  

有没有办法解决自动超载?

是的,如果您知道传递给方法的参数类型。就您而言,如果我理解正确,请Element

您的答案显示了如何解决修改原始代码的问题。接下来,我提出一个基于仅声明的constexpr函数的解决方案

  

还有另一种写更好的hasCount特征的方法吗?

我不知道是否更好,但是通常我更喜欢使用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 )

但是我更喜欢前面的解决方案,因为它需要更多的打字,但是更加灵活。

答案 1 :(得分:1)

从问题注释中,正确的方法是检查“ 调用表达式”(而不是方法的存在)。

因此, 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);
  }
}

答案 2 :(得分:0)

在这样的地方简单地遵循具有后备功能的单独的过载集很有帮助:

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(); }