问题
For logging code, I would like to detect whether given argument to a template function can be iterated over using the tools from Boost.Range or not. Obviously I need to instantiate different code whether it is or not, so I need SFINAE, possibly (well, certainly) combined with boost::enable_if. I've tried detecting whether begin
and end
free functions are defined, like this:
namespace is_range_impl {
template <typename T> T &make();
struct any { template <class T> any(T const&); };
struct not_range {};
not_range begin(const any &);
not_range end(const any &);
struct no_type { char x[8]; };
typedef char yes_type;
template <typename T> yes_type check(const T &t);
no_type check(const not_range &t);
using boost::begin;
using boost::end;
template <typename T> struct is_range_impl {
enum e {
value = (sizeof(check(begin(make<T>()))) == sizeof(yes_type) &&
sizeof(check(end(make<T>()))) == sizeof(yes_type)),
};
};
}
template <typename T>
struct is_range : public is_range_impl::is_range_impl<T> {};
template <typename T>
typename boost::disable_if<is_range<T> >::type repr(std::ostream &s, const T &v)
{ ... }
template <typename T>
typename boost::enable_if<is_range<T> >::type repr(std::ostream &s, const T &v)
{ ... }
But instead of silently failing when boost::begin
and boost::end
are not well-defined, it fails loudly with error that
'type' : is not a member of 'boost::mpl::eval_if_c<C,F1,F2>'
[C=false, F1=boost::range_const_iterator<void *>, F2=boost::range_mutable_iterator<void *>]
in the following code in boost/range/iterator.hpp:63
:
typedef BOOST_RANGE_DEDUCED_TYPENAME
mpl::eval_if_c< is_const<C>::value,
range_const_iterator< typename remove_const<C>::type >,
range_mutable_iterator<C> >::type type;
(I have boost 1.51, but 1.52 lists no changes and 1.53 alpha lists two bug-fixes, but neither seems to be related)
So is there some better way to detect ranges? While I am stuck with some C++03 compilers and probably will be for quite a long while longer, I would like to make it as easily switchable to C++11 (where free begin
and end
available via ADL appear to be all that's needed).
Specifically the compilers are Visual C++ 9.0 and GCC 4.5. SFINAE support is adequate in both.
回答1:
I believe the metafunction you need to use is has_range_iterator.
Running on LWS
#include <iostream>
#include <string>
#include <utility>
#include <boost/range.hpp>
template <typename T>
typename boost::disable_if<boost::has_range_iterator<T> >::type repr(const T &, const std::string& name)
{ std::cout << name << " is not a range" << std::endl; }
template <typename T>
typename boost::enable_if<boost::has_range_iterator<T> >::type repr(const T &, const std::string& name)
{ std::cout << name << " is a range" << std::endl; }
struct foo{};
struct bar
{
typedef int iterator;
typedef const int const_iterator;
int begin(){ return 0;};
int end(){ return 1;};
};
int main()
{
int i;
repr(i, "int");
int array[10];
repr(array, "int array");
std::string str;
repr(str, "std::string");
foo f;
repr(f, "foo");
bar b;
repr(b, "bar");
std::pair<int, int> p;
repr(p, "pair"); // it does make a mistake here, because std::pair<int, int> looks like range, but int is not valid iterator (cannot be dereferenced)
}
来源:https://stackoverflow.com/questions/14439479/detecting-whether-something-is-boost-range-with-sfinae