libcruft-util/types/description.hpp
Danny Robson 6cac76e210 types/description: make_description should be constexpr
This would be possible in the general case, but is fine for fundamental
types.
2021-04-07 12:53:49 +10:00

328 lines
11 KiB
C++

/*
* 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 <danny@nerdcruft.net>
*/
#pragma once
#include "traits.hpp"
#include "../std.hpp"
#include "../debug/panic.hpp"
#include "../typeidx.hpp"
#include <cstddef>
#include <type_traits>
#include <functional>
#include <utility>
namespace cruft::types {
///////////////////////////////////////////////////////////////////////////
/// Computes the number of elements of a scalar (ie, 1), or of statically
/// sized vector type
template <typename T, typename = void>
struct arity_trait { };
//-------------------------------------------------------------------------
// void arity is explicitly zero.
template <>
struct arity_trait<void, void> : public std::integer_sequence<std::size_t, 0> { };
//-------------------------------------------------------------------------
// All basic types are unit arity (except for void which is zero).
template <typename T>
struct arity_trait<
T,
std::enable_if_t<std::is_fundamental_v<T>>
> : public std::integral_constant<
std::size_t, 1u
> { };
//-------------------------------------------------------------------------
// Punt the handling of any enum to the underlying type.
template <typename EnumT>
struct arity_trait<
EnumT,
std::enable_if_t<std::is_enum_v<EnumT>>
> : public arity_trait<
std::underlying_type_t<EnumT>
> { };
//-------------------------------------------------------------------------
template <std::size_t S, typename T>
struct arity_trait<
std::array<T,S>
> : public std::integral_constant<std::size_t, S> { };
//-------------------------------------------------------------------------
template <typename T>
constexpr auto arity_v = arity_trait<T>::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 <typename T, typename = void>
struct category_traits;
template <> struct category_traits<u08> : public std::integral_constant<category,category::INTEGER> {};
template <> struct category_traits<u16> : public std::integral_constant<category,category::INTEGER> {};
template <> struct category_traits<u32> : public std::integral_constant<category,category::INTEGER> {};
template <> struct category_traits<u64> : public std::integral_constant<category,category::INTEGER> {};
template <> struct category_traits<i08> : public std::integral_constant<category,category::INTEGER> {};
template <> struct category_traits<i16> : public std::integral_constant<category,category::INTEGER> {};
template <> struct category_traits<i32> : public std::integral_constant<category,category::INTEGER> {};
template <> struct category_traits<i64> : public std::integral_constant<category,category::INTEGER> {};
template <> struct category_traits<f32> : public std::integral_constant<category,category::REAL> {};
template <> struct category_traits<f64> : public std::integral_constant<category,category::REAL> {};
template <> struct category_traits<bool> : public std::integral_constant<category,category::BOOL> {};
template <typename EnumT>
struct category_traits<
EnumT,
std::enable_if_t<
std::is_enum_v<EnumT>
>
> : public std::integral_constant<category,category::ENUM>
{ };
template <typename T>
constexpr auto category_traits_v = category_traits<T>::value;
//-------------------------------------------------------------------------
template <typename, typename = void>
struct signedness_traits;
template <> struct signedness_traits<u08> : public std::false_type {};
template <> struct signedness_traits<u16> : public std::false_type {};
template <> struct signedness_traits<u32> : public std::false_type {};
template <> struct signedness_traits<u64> : public std::false_type {};
template <> struct signedness_traits<i08> : public std::true_type {};
template <> struct signedness_traits<i16> : public std::true_type {};
template <> struct signedness_traits<i32> : public std::true_type {};
template <> struct signedness_traits<i64> : public std::true_type {};
template <> struct signedness_traits<f32> : public std::true_type {};
template <> struct signedness_traits<f64> : public std::true_type {};
template <> struct signedness_traits<bool> : public std::false_type {};
template <typename EnumT>
struct signedness_traits<
EnumT,
std::enable_if_t<
std::is_enum_v<EnumT>
>
>: public signedness_traits<std::underlying_type_t<EnumT>> {};
template <typename T>
constexpr auto signedness_traits_v = signedness_traits<T>::value;
//-------------------------------------------------------------------------
template <typename T>
constexpr
description
make_description (void) noexcept
{
if constexpr (std::is_void_v<T>) {
return {
.category = category::NONE,
.signedness = false,
.width = 0,
.arity = 1,
.alignment = 0,
.index = cruft::typeidx<T> (),
};
} else {
using inner_t = cruft::inner_type_t<T>;
return {
.category = category_traits_v<inner_t>,
.signedness = signedness_traits_v<inner_t>,
.width = sizeof (inner_t),
.arity = arity_v<T>,
.alignment = alignof (T),
.index = cruft::typeidx<T> (),
};
}
}
///////////////////////////////////////////////////////////////////////////
/// 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 <typename ValueT>
bool
is_fundamental_match (description const &desc)
{
return is_fundamental_match (desc, make_description<ValueT> ());
}
}
#include <iosfwd>
namespace cruft::types {
std::ostream& operator<< (std::ostream&, category);
std::ostream& operator<< (std::ostream&, description);
}