Danny Robson
6cac76e210
This would be possible in the general case, but is fine for fundamental types.
328 lines
11 KiB
C++
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);
|
|
} |