/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2018-2019 Danny Robson */ #pragma once #include "traits.hpp" #include "../std.hpp" #include "../debug/panic.hpp" #include "../typeidx.hpp" #include #include #include #include namespace cruft::types { /////////////////////////////////////////////////////////////////////////// /// Computes the number of elements of a scalar (ie, 1), or of statically /// sized vector type template struct arity_trait { }; //------------------------------------------------------------------------- // void arity is explicitly zero. template <> struct arity_trait : public std::integer_sequence { }; //------------------------------------------------------------------------- // All basic types are unit arity (except for void which is zero). template struct arity_trait< T, std::enable_if_t> > : public std::integral_constant< std::size_t, 1u > { }; //------------------------------------------------------------------------- // Punt the handling of any enum to the underlying type. template struct arity_trait< EnumT, std::enable_if_t> > : public arity_trait< std::underlying_type_t > { }; //------------------------------------------------------------------------- template struct arity_trait< std::array > : public std::integral_constant { }; //------------------------------------------------------------------------- template constexpr auto arity_v = arity_trait::value; /////////////////////////////////////////////////////////////////////////// enum class category { NONE, INTEGER, REAL, BOOL, ENUM, }; ///------------------------------------------------------------------------ /// A description of the type that makes up a scalar or vector type. /// /// A vector3f would be { REAL, sizeof(float), 3 }; /// /// DO NOT add a 'size' member function as it tends to be used in a way /// that confuses total byte size, element byte size, and arity. /// Instead: use the bytes member function, or the arity member variable. struct description { /// The fundamental nature of the type; is it integral, real, bool, etc. enum category category; /// Is the type considered to be signed? /// /// For some types, like void, this does not make sense. In this case /// the value must be false. /// /// For other types this value will almost certainly be one value in a /// realistic system; eg, signed floating point numbers. bool signedness; /// The number of bytes required for each of the value members. /// eg, sizeof(float) for a vector2f. The full size of the described /// type will be `arity * width` std::size_t width; /// The number of variables that make up an instance. std::size_t arity; std::size_t alignment; /// The value from typeidx for the constructed type (if it is known /// and has been registered), otherwise 0. int index; /// Returns the number of bytes required to store this type. constexpr std::size_t bytes (void) const noexcept { return width * arity; } }; //------------------------------------------------------------------------- constexpr bool operator== (description const &a, description const &b) noexcept { return a.category == b.category && a.signedness == b.signedness && a.width == b.width && a.arity == b.arity && a.alignment == b.alignment && (a.index == 0 || b.index == 0 || a.index == b.index); } //------------------------------------------------------------------------- constexpr bool operator!= (description const &a, description const &b) noexcept { return !(a == b); } /// A comparator that compares values as equal if they are an enum and the /// underlying_type of said enum. /// /// This is distinct from the raw equality operator so as to not pessimise /// the usability of the equality operator (for tasks that required strict /// equality). struct underlying_comparator { bool operator() ( description const &a, description const &b ) const noexcept { // If one of the descriptions is ENUM then both need to be either ENUM // or INTEGER (because we try to compare enums and their underlying // types as equal). if (a.category == category::ENUM || b.category == category::ENUM) { if (a.category != category::ENUM && a.category != category::INTEGER) return false; if (b.category != category::ENUM && b.category != category::INTEGER) return false; // Indices only need to match if they're both an ENUM, // otherwise we're testing the underlying type. if (a.category == category::ENUM && b.category == category::ENUM) if (a.index != b.index) return false; } else { if (a.index != b.index) return false; } // All details other must be identical; return a.signedness == b.signedness && a.width == b.width && a.arity == b.arity && a.alignment == b.alignment; }; }; //------------------------------------------------------------------------- template struct category_traits; template <> struct category_traits : public std::integral_constant {}; template <> struct category_traits : public std::integral_constant {}; template <> struct category_traits : public std::integral_constant {}; template <> struct category_traits : public std::integral_constant {}; template <> struct category_traits : public std::integral_constant {}; template <> struct category_traits : public std::integral_constant {}; template <> struct category_traits : public std::integral_constant {}; template <> struct category_traits : public std::integral_constant {}; template <> struct category_traits : public std::integral_constant {}; template <> struct category_traits : public std::integral_constant {}; template <> struct category_traits : public std::integral_constant {}; template struct category_traits< EnumT, std::enable_if_t< std::is_enum_v > > : public std::integral_constant { }; template constexpr auto category_traits_v = category_traits::value; //------------------------------------------------------------------------- template struct signedness_traits; template <> struct signedness_traits : public std::false_type {}; template <> struct signedness_traits : public std::false_type {}; template <> struct signedness_traits : public std::false_type {}; template <> struct signedness_traits : public std::false_type {}; template <> struct signedness_traits : public std::true_type {}; template <> struct signedness_traits : public std::true_type {}; template <> struct signedness_traits : public std::true_type {}; template <> struct signedness_traits : public std::true_type {}; template <> struct signedness_traits : public std::true_type {}; template <> struct signedness_traits : public std::true_type {}; template <> struct signedness_traits : public std::false_type {}; template struct signedness_traits< EnumT, std::enable_if_t< std::is_enum_v > >: public signedness_traits> {}; template constexpr auto signedness_traits_v = signedness_traits::value; //------------------------------------------------------------------------- template constexpr description make_description (void) noexcept { if constexpr (std::is_void_v) { return { .category = category::NONE, .signedness = false, .width = 0, .arity = 1, .alignment = 0, .index = cruft::typeidx (), }; } else { using inner_t = cruft::inner_type_t; return { .category = category_traits_v, .signedness = signedness_traits_v, .width = sizeof (inner_t), .arity = arity_v, .alignment = alignof (T), .index = cruft::typeidx (), }; } } /////////////////////////////////////////////////////////////////////////// /// Tests if the underlying scalar value specified by two descriptions /// matches. /// /// This deliberately ignores arity, alignment, and index. /// /// It is provided to assist systems that needs to coerce values and /// assumes the caller is prepared to deal with indexing, alignment, and /// interpretation. inline bool is_fundamental_match (description const &a, description const &b) { // Both categories must match, or one must be ENUM and the other must // be INTEGER. if (a.category != b.category) { if (a.category == category::ENUM) if (b.category != category::INTEGER) return false; if (b.category == category::ENUM) if (a.category != category::INTEGER) return false; } return a.signedness == b.signedness && a.width == b.width; } ///------------------------------------------------------------------------ /// Tests if a type matches the underlying scalar value specified by a /// description. /// /// This exists as a convenience wrapper for `is_fundemental_wrapper` /// with two descriptions. template bool is_fundamental_match (description const &desc) { return is_fundamental_match (desc, make_description ()); } } #include namespace cruft::types { std::ostream& operator<< (std::ostream&, category); std::ostream& operator<< (std::ostream&, description); }