You can create a constrain for a class. And another constrain for the same class. Isn't this crazy stuff?
With this we can build something like an identity function for types, like std::type_identity, for value_type. It's something like the C++17 non-member functions size() and empty(). Why must value_type be a member? It can be a non-member trait.
It turns out, there is already this functionality in C++ 20 ranges TS in std::experimental::ranges::value_type. It's really hard to catch up... but implemented it for you. And users may specialize value_type I also put my part for arithmetic types here.
This is part of ACPL functional.hpp file.
/// Primary template is an empty struct. /// \note this is in C++20 ranges now except for the arithmetic types overload /// https://en.cppreference.com/w/cpp/experimental/ranges/iterator/value_type template<class I> struct value_type { }; /// Specialization for pointers. /// If T is an object type, provides a member type type equal to std::remove_cv_t<T>. /// Otherwise, there is no member type. template<class T> struct value_type<T*> { using type = std::remove_cv_t<T>; }; /// Specialization for array types. template<class I> requires(std::is_array<I>::value) struct value_type<I> : value_type<std::decay_t<I>> { }; /// Specialization for const-qualified types. template<class T> struct value_type<const T> : value_type<std::decay_t<T>> { }; /// Specialization for types that define a public and accessible member type value_type. /// If T::value_type is an object type, provides a member type type equal to T::value_type. /// Otherwise, there is no member type. /// \todo requires requires template<class T> requires requires{ typename T::value_type; } struct value_type<T> { using type = typename T::value_type; }; /// Specialization for types that define a public and accessible member type element_type. /// (e.g., std::shared_ptr). /// If T::element_type is an object type, provides a member type type equal to std::remove_cv_t<typename T::element_type>. /// Otherwise, there is no member type. template<class T> requires requires{ typename T::element_type; } struct value_type<T> { using type = typename T::element_type; }; /// Helper alias template template<class T> using value_type_t = typename value_type<T>::type; /// ACPL specialization for arithmetic types template<class T> requires(std::is_arithmetic_v<T>) struct value_type<T> { using type = T; }; // Specialization for pointers. static_assert(std::is_same_v<int, acpl::value_type_t<int*>>); // Specialization for array types. static_assert(std::is_same_v<int, acpl::value_type_t<int[]>>); // Specialization for const-qualified types. static_assert(std::is_same_v<int, acpl::value_type_t<const int*>>); // Specialization for types that define a public and accessible member type value_type. static_assert(std::is_same_v<int, acpl::value_type_t<std::array<int,1>>>); // Specialization for types that define a public and accessible member type element_type. static_assert(std::is_same_v<int, acpl::value_type_t<std::unique_ptr<int>>>); // ACPL specialization for arithmetic types static_assert(std::is_same_v<int, acpl::value_type_t<int>>); static_assert(std::is_same_v<int, acpl::value_type_t<const int>>);
For the record, this is my first try:
template<typename T> struct value_type { }; template<typename T> requires(std::is_scalar_v<T>) struct value_type<T> { using type = T; }; template<typename T> requires(not std::is_scalar_v<T>) struct value_type<T> { using type = typename T::value_type; }; template<typename T> using value_type_t = typename value_type<T>::type; static_assert(std::is_same_v<int, value_type_t<int>> ); static_assert(std::is_same_v<int, value_type_t<std::vector<int>>> );