I get stucked on the way of writing my own implementation of STL. The question occurred when I tried to implement std::pointer_traits. By default, std::pointer_traits evaluate his typedef's based on the passed parameter, like follow:
template <typename Ptr>
struct pointer_traits
{
typedef typename Ptr::element_type element_type;
...
};
But here is where the standard says that poiter_traits are ill-formed. When Ptr has no predefined typedef element_type and so on, the compiled error is guaranteed. My implementation offers a new approach. Whenever we try to evaluate typedef's, we will choose between two options:
When Ptr has desired typedef's, we take they as well
When Ptr has not targed typedef's, we will define them as undefined (std::__undefined)
Functions and other stuff are intentionally omitted for simplicity
template <typename Ptr>
class pointer_traits
{
private:
template <typename T, typename = void>
struct __ptr
{
typedef __undefined ptr;
};
template <typename T>
struct __ptr<T, vstl::__void_t<typename T::pointer*>>
{
typedef typename T::pointer ptr;
};
template <typename T, typename = void>
struct __eltype
{
typedef __undefined element_type;
};
template <typename T>
struct __eltype<T, vstl::__void_t<typename T::element_type*>>
{
typedef typename T::element_type element_type;
};
template <typename T, typename = void>
struct __difftype
{
typedef std::ptrdiff_t difference_type;
};
template <typename T>
struct __difftype<T, vstl::__void_t<typename T::difference_type*>>
{
typedef typename T::difference_type difference_type;
};
public:
using pointer = typename __ptr<Ptr>::ptr;
using element_type = typename __eltype<Ptr>::element_type;
using difference_type = typename __difftype<Ptr>::difference_type;
};
This approach will increase pointer_traits stability, but may lead to a misunderstanding. When a type does not provide desired functionality, why we should even instantiate a pointer_traits??? And at a single moment, the state of pointer_traits instance will be undefined for itself. Some members will be defined, some will not.
Does my implementation make sense?
Related
I want to have a type which depends on some other class X having X::value_type.
If there is no such typedef inside the other class, I want to just use X by itself.
So I am looking for code like:
TypeChecker<X>::value_type // Here, value_type might be X or X::value_type, depending on X.
But my initial attempts are failing, see below:
Here's a program that does not compile:
#include<type_traits>
template<typename T>
struct TypeChecker {
typedef typename std::conditional<
std::is_class<T>::value,
typename T::value_type, // I believe the error is due to this line (could be wrong)
T>::type value_type;
};
int main()
{
TypeChecker<int>::value_type x = 3;
return 0;
}
It gives: error: 'int' is not a class, struct, or union type
You can try online: godbolt link
I'm confused, because I thought std::conditional would choose the right branch, but it seems to be evaluating both of them, in some sense. At least, enough to cause a compile error.
Help?
The problem is T::value_type is passed to std::conditional as the template argument, when T is int the expression itself is invalid.
You can do it with class template and partial specialization instead.
template<typename T, typename = void>
struct TypeChecker {
typedef T value_type;
};
template<typename T>
struct TypeChecker<T, typename std::enable_if<std::is_class<T>::value>::type> {
typedef typename T::value_type value_type;
};
BTW: std::is_class seems to be much loose condition; you can constrain it as has the member type value_type, e.g.
template<typename T, typename = void>
struct TypeChecker {
typedef T value_type;
};
template<typename T>
struct TypeChecker<T, std::void_t<typename T::value_type>> {
typedef typename T::value_type value_type;
};
std::conditional does not "short-circuit". For std::conditional<C,X,Y> to even be instantiated as a class type, the three template arguments must all be actual types. There's no way to have a template that can be used with one of its template parameters representing an illegal or unknown type, even if that parameter doesn't directly matter.
Some solutions to your TypeChecker:
// C++17, using partial specialization SFINAE:
template <typename T, typename Enable = void>
struct TypeChecker {
using value_type = T;
};
template <typename T>
struct TypeChecker<T, std::void_t<typename T::value_type>> {
using value_type = typename T::value_type;
};
// C++11 or later, using function SFINAE:
template <typename T>
struct type_identity { using type = T; };
template <typename T>
type_identity<typename T::value_type> TypeChecker_helper(int); // not defined
template <typename T>
type_identity<T> TypeChecker_helper(...); // not defined
template <typename T>
struct TypeChecker {
using value_type = typename decltype(TypeChecker_helper<T>(0))::type;
};
(Aside: I'm always a bit suspicious of templates that automatically "unwrap" things, like this TypeChecker. It could thwart code which specifically wants to use a container or iterator as a value directly.)
I am writing a program that will deal heavily with strings of varying types (i.e. std::string, std::string_view, const char*, char[], and friends with varying CharT). So, I am interesting in writing a trait that can abstract getting the value type of generic array-like structures. For the previously listed types, the correct return type would be char.
However, I can not seem to get the implementation correct to allow for generic types. Consider the following attempt:
template<class T>
struct value_type{
using type = std::conditional_t<
std::is_class_v<T>,
typename T::value_type,
std::conditional_t<
std::is_pointer_v<T>,
std::remove_pointer_t<T>,
// etc.
>;
};
This, of course, does not compile because not all types have a nested value_type typedef.
Is there any way to implement this trait effectively?
You might use specialization (and SFINAE):
template<class T, Enabler = void>
struct value_type;
template<class T>
struct value_type<T, std::void_t<typename T::value_type>>
{
using type = typename T::value_type;
};
template<class T>
struct value_type<const T*>
{
using type = T;
};
template<class T, std::size_t N>
struct value_type<const T[N]>
{
using type = T;
};
// ...
You can defer to a helper class template. Fortunately type aliases aren't instantiated during an implicit instantiation:
template <class T>
struct get_value_type {
using type = typename T::value_type;
};
template <class T>
struct value_type {
using type = typename std::conditional_t<
std::is_class_v<T>,
get_value_type<T>,
std::conditional<std::is_pointer_v<T>, std::remove_pointer_t<T>, void>
>::type;
};
This will also require you to change the second std::conditional_t to std::conditional so that we can get ::type from whatever type is returned.
Suppose I have a class template like this:
template<typename T>
struct MyClass
{
typedef T inferred_type;
};
And somewhere else I see a deferred type of
typedef MyClass<int>* param_type;
But MyClass<int>* is deferred and I don't know that int is used for instantiation here. I'm actually accessing another typedef for MyClass<int>*.
Can I somehow get to MyClass<int>::inferred_type from that pointer typedef?
EDIT:
Regarding RiaD's answer, the following line does what I need:
typedef typename std::iterator_traits<param_type>::value_type::inferred_type TheDecucedInferredType;
Note that typename is only needed since param_type itself is a template parameter, within the context I see there.
You can use a "type function":
template<typename T> struct remove_pointer { typedef T type; };
template<typename T> struct remove_pointer<T*> { typedef T type; };
Then if ptrtype is your pointer typedef (or template argument, or whatever), you can write
MyClass<remove_pointer<ptrtype>::type>::value_type
C++11 conveniently has such a template remove_pointer predefined.
You may try to use iterator_traits
typename std::iterator_traits<Pointer>::value_type //if Pointer is MyClass<int>*, then it will be MyClass<int>
After that you may wrote value_type once more to get int.
It will work with pointers and standard iterators.
In C++11 and only if MyClass definition is visible you can do
typedef MyClass<int>* type;
std::remove_reference<decltype(*std::declval<type>())>::type::value_type
Without C++11 you can do
template<typename T>
struct get_value_type;
template<typename T>
struct get_value_type<T*>
{
typedef typename T::value_type value_type;
};
typedef MyClass<int>* type;
get_value_type<type>::value_type
Is there a way to do this with some c++11 or at most a boost library?
#include <iostream>
#include <typeinfo>
using namespace std;
template <typename T> class remove_all_pointers{
public:
typedef T type;
};
template <typename T> class remove_all_pointers<T*>{
public:
typedef typename remove_all_pointers<T>::type type;
};
int main(){
//correctly prints 'i' on gcc
cout<<typeid(remove_all_pointers<int****>::type).name()<<endl;
}
That doesn't quite work for all pointer types. You need to account for different cv-qualifiers as well:
template <typename T> class remove_all_pointers<T* const>{
public:
typedef typename remove_all_pointers<T>::type type;
};
template <typename T> class remove_all_pointers<T* volatile>{
public:
typedef typename remove_all_pointers<T>::type type;
};
template <typename T> class remove_all_pointers<T* const volatile >{
public:
typedef typename remove_all_pointers<T>::type type;
};
Since C++17 you can create a readable, simple and cv-qualifier aware meta function.
Use it like:
int main()
{
remove_all_pointers_t<int* const* volatile* const volatile*> v = 42;
return 0;
}
C++20
#include <type_traits>
template<typename T>
struct remove_all_pointers : std::conditional_t<
std::is_pointer_v<T>,
remove_all_pointers<
std::remove_pointer_t<T>
>,
std::type_identity<T>
>
{};
template<typename T>
using remove_all_pointers_t = typename remove_all_pointers<T>::type;
C++17
In C++17 std::type_identity isn't available yet and std::identity isn't available anymore, hence you need to create your own 'identity' meta function:
#include <type_traits>
// your custom 'identity' meta function
template <typename T>
struct identity
{
using type = T;
};
template<typename T>
struct remove_all_pointers : std::conditional_t<
std::is_pointer_v<T>,
remove_all_pointers<
std::remove_pointer_t<T>
>,
identity<T>
>
{};
template<typename T>
using remove_all_pointers_t = typename remove_all_pointers<T>::type;
Neither Boost nor C++11 features such a trait template. But your code should work.
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++?
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);
}
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...)