More C++ Idioms/Traits

Traits

edit

Intent

edit

To be able to use a common interface to get information about a type, even when the type cannot be changed to conform to any common interface.

Also Known As

edit

Motivation

edit

A function template might need to behave differently for different types based on information about those types. If only user-defined types can have members and member types that encode information about the enclosing type, then there will need to be a lot of special cases for builtin types like integers and pointers. By using a separate traits class with a primary specialization for types that can be modified to include certain members and specializations for types that cannot, functions can still use a common interface by using the traits class.

Solution and Sample Code

edit

All traits classes have zero size and only include static members or subtypes. The primary specialization usually gets the values or types from members of the class whose traits are gotten. (In the below example, the member types come from base classes.) The specializations get the values or types from other means in such a way that the interface remains consistent. The cool thing about traits is that they are non-intrusive. This allows them to be applied to built-in types, like the specialization of container_traits for arrays below. This also allows retroactive modeling.

namespace detail{
    template <class C>
    concept container = requires {
        typename C::value_type;
        typename C::size_type;
        typename C::difference_type;
        typename C::reference;
        typename C::const_reference;
        typename C::iterator;
        typename C::const_iterator;
    };

    template <class C>
    struct container_mixin{};
    template <container C>
    struct container_mixin<C>{
        using value_type =       typename C::value_type;
        using size_type =        typename C::size_type;
        using difference_type =  typename C::difference_type;
        using reference =        typename C::reference;
        using const_reference =  typename C::const_reference;
        using iterator =         typename C::iterator;
        using const_iterator =   typename C::const_iterator;
    };

    template <class C>
    struct allocator_mixin{};
    template <container C>
    requires requires {typename C::allocator_type;}
    struct allocator_mixin<C>{
        using allocator_type = typename C::allocator_type;
    };

    template <class C>
    struct reverse_iterator_mixin{};
    template <container C>
    requires requires {
        typename C::reverse_iterator;
        typename C::const_reverse_iterator;
    }
    struct reverse_iterator_mixin<C>{
        using reverse_iterator =       typename C::reverse_iterator;
        using const_reverse_iterator = typename C::const_reverse_iterator;
    };

    template <class C>
    struct pointer_mixin{};
    template <container C>
    requires requires {
        typename C::pointer;
        typename C::const_pointer;
    }
    struct pointer_mixin<C>{
        using pointer =       typename C::pointer;
        using const_pointer = typename C::const_pointer;
    };
}

// Primary template
template <class C>
struct container_traits :
    public detail::container_mixin<C>,
    public detail::allocator_mixin<C>,
    public detail::reverse_iterator_mixin<C>,
    public detail::pointer_mixin<C>
{};

// Specialization for arrays, cannot add typedef members to arrays
template <class T, std::size_t N>
struct container_traits<T[N]>{
    using value_type =             T;
    using size_type =              std::size_t;
    using difference_type =        std::ptrdiff_t;
    using reference =              T&;
    using const_reference =        const T&;
    using pointer =                T*;
    using const_pointer =          const T*;
    using iterator =               T*;
    using const_iterator =         const T*;
    using reverse_iterator =       std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
};

Known Uses

edit
  • std::allocator_traits
  • std::iterator_traits
  • std::pointer_traits
  • std::numeric_limits
  • std::common_type
  • std::basic_common_reference
edit

References

edit