Reputation: 1246
I'm in the process of writing up an STL-like library for learning purposes. All of the collections extend a class called Iterable
which contains wrapping functions for all of the functions found in <algorithm>
. For example, it allows vec.each([](T t){...});
which I strongly prefer over the verbose std::for_each
. The function giving me problems is count
- I want to overload Iterable::count
so it combines the behaviour of both std::count
and std::count_if
depending on the argument type but I'm running into a strange error.
Iterable.h
virtual int count(const T& value) const {
return std::count(begin(), end(), value);
}
virtual int count(std::function<bool(T&)> predicate) {
return std::count_if(begin(), end(), predicate);
}
virtual int count(std::function<bool(const T&)> predicate) const {
return std::count_if(begin(), end(), predicate);
}
main.cpp
Vector<int> vec; // extends Iterable
vec.add(0);
vec.add(1);
vec.count([](int i){ return i == 0; }); // compiles and works fine
vec.count(0); // error c2666: 3 overloads have similar conversions
I should note that changing the count_if
wrapper function names to count_if
does work and resolves the ambiguity, but I'd prefer to have them named count
and also to figure out why there is ambiguity in the first place.
From what I interpret, the compiler is trying to make a new std::function
using the template <class F> function(F f)
ctor, then runs into the ambiguity. Is that the case? It seems odd since the line below fails to compile as well.
std::function<bool(int)> f(0); // error C2064: term does not evaluate to a function taking 1 arguments
Any insights or potential fixes are much appreciated.
Forgot to say; using visual studio 2012, nov 2012 ctp compiler
Upvotes: 3
Views: 662
Reputation: 7330
The problem is that 0 is ambiguous here, it can be interpreted as a null pointer or an int, which makes it match both the std::function
constructor and the more general const T& value
(both require a conversion).
If you don't want to change the interface, you can just create a very simple function template to deduce and dispatch the arguments.
C++11 version:
template<typename U>
int count(U&& value) const {
return count_impl(std::forward<U>(value));
}
This works because the function template type deduction rules don't have that ambiguity, they never treat 0 as a null pointer.
So your interface is now:
virtual int count_impl(const T& value) const {
return std::count(v.begin(), v.end(), value);
}
virtual int count_impl(std::function<bool(T&)> predicate) {
return std::count_if(v.begin(), v.end(), predicate);
}
virtual int count_impl(std::function<bool(const T&)> predicate) const {
return std::count_if(v.begin(), v.end(), predicate);
}
template<typename U>
int count(U&& value) const {
return count_impl(std::forward<U>(value));
}
And you can use it naturally:
int main(){
Vector<int> vec; // extends Iterable
vec.count([](int i){ return i == 0; }); // compiles and works fine
vec.count(0); // no problem, calls virtual int count_impl(const T& value) const
}
Upvotes: 3
Reputation: 275820
std::function<Sig>
in the published C++11 standard without errata contains a constructor that thinks it can consume anything, as far as its signature is concerned. If you pass it things it cannot consume (things that are not callable), it fails to compile.
Overload resolution occurs earlier (based on shallower information) than the compile failure. It matches on signatures, not implementations.
A bug report and a fix was proposed, so some C++11 compilers can fix this, and all C++14 compilers must fix this.
VS2012 has limited SFINAE overload resolution capabilities. But one approach would look like:
template<class Sig, class=void>
struct is_filter_on : std::false_type{};
template<class F, class Arg>
struct is_filter_on< F(Arg),
typename std::enable_if<std::is_convertible<
typename std::result_of<F(Arg)>::type
,bool>::value>::type
> : std::true_type{};
which is an attempt at a traits class that tells you if F(Arg)
is a bool
-returning "filter" on values of type Arg
.
template<class X>
size_t count(X&& x) const {
return count( std::forward<X>(x), is_filter_on< X&(T const&) >{} );
}
template<class X>
size_t count(X&& x) {
return count( std::forward<X>(x), is_filter_on< X&(T&) >{} );
}
template<class F>
size_t count(F&& f, std::true_type) const {
return std::count_if( begin(), end(), std::forward<F>(f) );
}
template<class F>
size_t count(F&& f, std::true_type) {
return std::count_if( begin(), end(), std::forward<F>(f) );
}
template<class X>
size_t count(X&& x, std::false_type) const {
return std::count( begin(), end(), std::forward<X>(x) );
}
template<class X>
size_t count(X&& x, std::false_type) {
return std::count( begin(), end(), std::forward<X>(x) );
}
but I have no idea of MSVC2012 will work with the above.
Here I use tag dispatching to pick which version of count
I call. The traits class is_filter_on
does a test to determine if the pseudo-expression F(Arg)
is filter-like. If so, we dispatch to the std::count_if
. Otherwise, we dispatch to the std::count
version.
Upvotes: 4