12

I wrote the following template function, which checks whether an arbitary container contains a specific element:

template<template<class, class...> class container_t, class item_t, class... rest_t>
bool contains(const container_t<item_t, rest_t...> &_container, const item_t &_item) {
    for(const item_t &otherItem : _container) {
        if(otherItem == _item) { return true; }
    }
    return false;
}

This works well for most containers. However for all kinds of sets (and maps) it is sub optimal since there we could use:

template<template<class, class...> class set_t, class item_t, class... rest_t>
bool contains(const set_t<item_t, rest_t...> &_set, const item_t &_item) {
    return _set.count(_item) > 0;
}

Obviously we can't use both templates simultaneously because of ambiguity. Now I am looking for a way to use std::enable_if to enable the to first template if container_t does not provide a count member function and the second template if it does. However I can't figure out how to check for a specif member function (using C++11).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Haatschii
  • 9,021
  • 10
  • 58
  • 95
  • 4
    If you are willing to add boost as a dependency then [boost.TTI](http://www.boost.org/doc/libs/1_57_0/libs/tti/doc/html/index.html) has what you are looking for in the ``BOOST_TTI_HAS_MEMBER_FUNCTION`` macro – Simon Gibbons Apr 08 '15 at 17:17
  • 2
    Are you totally sure you want to do this? In the standard library they make you spell out exactly which `find` form you want to use to make it explicitly clear when you're doing a linear search and when you're using a more efficient mechanism. As a side note if you really do want to do this, definitely use `find` instead of `count`, it's just avoiding premature pessimization. – Mark B Apr 08 '15 at 17:21
  • @SimonGibbons: Thanks, it looks like what I need. However I would prefer not to depend on boost at the moment. – Haatschii Apr 08 '15 at 17:28
  • @MarkB: As far as I understand `std::find` also uses different algorithms depending on whether or not we have an ordered container, doesn't it? At least http://en.cppreference.com/w/cpp/algorithm/find writes that the complexity is at most linear, i.e. can be less than linear for suitable containers. However correct me if I am wrong. – Haatschii Apr 08 '15 at 17:31
  • @Haatschii The standard just says "at most last-first comparisons", so the assumption is that it's linear for all container types. – Mark B Apr 08 '15 at 17:35

1 Answers1

16

C++14 feature, reimplemented:

template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;

A mini metaprogramming library:

template<class...>struct types{using type=types;};
namespace details {
  template<template<class...>class Z, class types, class=void>
  struct can_apply : std::false_type {};
  template<template<class...>class Z, class...Ts>
  struct can_apply< Z, types<Ts...>, void_t< Z<Ts...> > >:
    std::true_type
  {};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z,types<Ts...>>;

can_apply< some_template, args... > inherits from true_type iff some_template<args...> is a valid expression (in the immediate context).

Now for your problem:

template<class T, class I>
using dot_count_type = decltype( std::declval<T>().count(std::declval<I>()) );

template<class T, class I>
using has_dot_count = can_apply<dot_count_type, T, I>;

and has_dot_count is a traits class that inherits from true_type iff T.count(I) is a valid expression.

namespace details {
  template<class C, class I>
  bool contains(std::false_type, C const& c, I const& i) {
    for(auto&& x:c) {
      if(x == i) { return true; }
    }
    return false;
  }
  template<class C, class I>
  bool contains(std::true_type, C const& c, I const& i) {
    return c.count(i) != 0;
  }
}
template<class C, class I>
bool contains( C const& c, I const& i ) {
  return details::contains( has_dot_count<C const&,I const&>{}, c, i );
}

which uses tag dispatching instead of SFINAE.

Using find seems like a better idea than .count as an aside. In fact, in one case you should use .find the other find. In both cases you should use using std::end; auto e = end(c);.

As an aside, MSVC 2013 (I don't know about 2015) doesn't handle this kind of SFINAE used above. They call it "expression SFINAE". They have custom extensions to detect the existence of a member function. But that is because they are far from C++11 standard compliant.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    VS 2015 (at least preview version) [doesn't support it as well](http://blogs.msdn.com/b/vcblog/archive/2014/11/17/c-11-14-17-features-in-vs-2015-preview.aspx) – Anton Savin Apr 08 '15 at 18:00
  • Interesting approach. Works well for me (GCC 4.8). Thank you very much. – Haatschii Apr 08 '15 at 18:14