Can C++'s value_type be extended from iterator_traits to all types?
-
21-09-2019 - |
Question
I would like to create a construct similar to std::iterator_traits::value_type that can work seamlessly for all types using the same syntax. Imagine we have the following:
template <typename T>
struct value_type {
typedef T type;
};
#define VALUE_TYPE(T) typename value_type<T >::type
This will work for POD types. I can specialize it for my own class:
struct MyClass {
typedef float value_type;
};
template <>
struct value_type<MyClass> {
typedef MyClass::value_type type;
};
though I would prefer to avoid extra value_type instantiations in an ideal world.
The problem is with STL iterators. I need a specialization that gets me to the iterator hierarchy. This fails because the compiler chooses the base case:
template <>
struct value_type<std::_Iterator_base_aux> { // MSVC implementation
typedef value_type type;
};
Choosing a class higher up the hierarchy (_Iterator_with_base would be most natural because that is where value_type is defined) fails because it requires specifying all the iterator traits as template arguments.
Is what I'm trying to do even possible in C++?
Solution
You can use SFINAE to detect the presence of the value_type
typedef. No need to specialize for individual types (which might not be possible, since you'd be relying entirely on internal implementation details).
#include <vector>
template <class T>
struct has_value_type
{
typedef char true_type;
typedef char false_type[2];
//template not available if there's no nested value_type in U's scope
template <class U>
static true_type test(typename U::value_type* );
//fallback
template <class U>
static false_type& test(...);
//tests which overload of test is chosen for T
static const bool value = sizeof(test<T>(0)) == sizeof(true_type);
};
template <class T, bool b>
struct value_type_impl;
template <class T>
struct value_type_impl<T, false> //if T doesn't define value_type
{
typedef T type;
};
template <class T>
struct value_type_impl<T, true> //if T defines value_type
{
typedef typename T::value_type type;
};
template <class T>
struct value_type: value_type_impl<T, has_value_type<T>::value>
{
};
struct MyClass {
typedef float value_type;
};
template <class T>
int foo(T )
{
return typename value_type<T>::type();
}
int main()
{
foo(MyClass());
std::vector<int> vec;
foo(vec.begin());
foo(10);
}
OTHER TIPS
UncleBens
has used SFINAE, but there is actually simpler:
template <class T>
struct value_type
{
typedef typename T::value_type type;
};
Now, if you want to use it with a class you control, the easiest way is:
struct MyClass { typedef float value_type; };
BOOST_MPL_ASSERT((boost::is_same< MyClass::value_type,
typename value_type<MyClass>::type >));
And if you want to use for a class you do not control, you still have specialization:
struct ThirdPartyClass {};
template <>
struct value_type<ThirdPartyClass> { typedef int type; }
If you try to use value_type
for a class that does not have an inner typedef
and for which no specialization is available, it's a compilation error (and a message you are not likely to understand at first glance...)