The following code of mine should detect whether T
has begin
and end
methods:
template <typename T>
struct is_container
{
template <typename U, typename U::const_iterator (U::*)() const,
typename U::const_iterator (U::*)() const>
struct sfinae {};
template <typename U> static char test(sfinae<U, &U::begin, &U::end>*);
template <typename U> static long test(...);
enum { value = (1 == sizeof test<T>(0)) };
};
And here is some test code:
#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>
int main()
{
std::cout << is_container<std::vector<std::string> >::value << ' ';
std::cout << is_container<std::list<std::string> >::value << ' ';
std::cout << is_container<std::set<std::string> >::value << ' ';
std::cout << is_container<std::map<std::string, std::string> >::value << '\n';
}
On g++ 4.5.1, the output is 1 1 1 1
. On Visual Studio 2008, however, the output is 1 1 0 0
. Did I do something wrong, or is this simply a VS 2008 bug? Can anyone test on a different compiler? Thanks!
Why are you going to all that effort? If you want to check if
U::begin()
exists, why not try it?In addition to checking for the existance of
U::begin()
andU::end()
, this also checks whether they return something that is convertible to aconst_iterator
. It also avoids the pitfall highlighted by Stephan T. Lavavej by using a call expression that must be supported, instead of assuming a particular signature.[edit] Sorry, this relied on VC10's template instantiation. Better approach (puts the existance check in the argument types, which do participate in overloading):
This probably should be a comment, but I don't have enough points
@MSalters
Even though your
is_container
works (almost) and I've used your code myself, I've discovered two problems in it.First is that type
deque<T>::iterator
is detected as a container (in gcc-4.7). It seems thatdeque<T>::iterator
hasbegin
/end
members andconst_iterator
type defined.2nd problem is that this code is invalid according to GCC devs. I qoute: values of default arguments are not part of the function type and do not take part in deduction. See GCC bug 51989
I am currently using this (C++11 only) for
is_container<T>
:Stephan T. Lavavej has this to say:
So I guess I'm going to use the simpler version that only checks for the nested
const_iterator
type.So, here's how I go about debugging these things.
First, comment out the negative alternative so you get an error instead of just a mismatch. Next, try to instantiate the type you're putting in the function with one of the items that do not work.
At this step, I was able to instantiate your sfinae object but it still wasn't working. "This lets me know it IS a VS bug, so the question then is how to fix it." -- OBS
VS seems to have troubles with SFINAE when done the way you are. Of course it does! It works better when you wrap up your sfinae object. I did that like so:
Still wasn't working, but at least I got a useful error message:
error C2440: 'specialization' : cannot convert from 'overloaded-function' to 'std::_Tree_const_iterator<_Mytree> (__thiscall std::set<_Kty>::* )(void) const'
This lets me know that
&U::end
is not sufficient for VS (ANY compiler) to be able to tell which end() I want. A static_cast fixes that:Put it all back together and run your test program on it...success with VS2010. You might find that a static_cast is actually all you need, but I left that to you to find out.
I suppose the real question now is, which compiler is right? My bet is on the one that was consistent: g++. Point to the wise: NEVER assume what I did back then.
Edit: Jeesh... You are wrong!
Corrected version:
-- The debugging above is sensible, but the assumption about the compiler was wrong headed. G++ should have failed for the reason I emphasized above.
With C++11, there are now better ways to detect this. Instead of relying on the signature of functions, we simply call them in an expression SFINAE context:
Live example on Ideone. The
int
andlong
parameters are only to disambiguate overload resolution when the container offers both (or ifiterator
istypedef const_iterator iterator
, likestd::set
is allowed to) - literal0
is of typeint
and forces the first overload to be chosen.