Skip to content
Permalink
Browse files
Introducing: Common Concepts
As Dolphin Emulator upgrades to C++20, concepts ought to be applied around the codebase to clean up static assertions and primitive SFINAE.  Also, a clang-format rule for concepts has been added.

Unfortunately, the Build-bot infrastructure currently has outdated C++ Standard Libraries despite supporting C++20 features.  We can work around this issue for the time being by wielding the other new header file introduced by this commit: 'Common/Future/CppLibConcepts.h'.

Finally, TypeUtils.h has been re-licensed under the CC0-1.0 public-domain-equivalent license with permission from Pokechu22, its original author and only contributor to it thus far.
  • Loading branch information
Minty-Meeo committed Apr 14, 2023
1 parent ae18aa0 commit bfcf099
Show file tree
Hide file tree
Showing 44 changed files with 363 additions and 318 deletions.
@@ -34,6 +34,7 @@ BraceWrapping:
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: false
BreakBeforeConceptDeclarations: true
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 100
CommentPragmas: '^ (IWYU pragma:|NOLINT)'
@@ -3,21 +3,20 @@
#pragma once

#include <cstddef>
#include <type_traits>

#include "Common/Future/CppLibConcepts.h"

namespace Common
{
template <typename T>
template <std::unsigned_integral T>
constexpr T AlignUp(T value, size_t size)
{
static_assert(std::is_unsigned<T>(), "T must be an unsigned value.");
return static_cast<T>(value + (size - value % size) % size);
}

template <typename T>
template <std::unsigned_integral T>
constexpr T AlignDown(T value, size_t size)
{
static_assert(std::is_unsigned<T>(), "T must be an unsigned value.");
return static_cast<T>(value - value % size);
}

@@ -9,6 +9,8 @@

#include "Common/CommonTypes.h"

#include "Common/Future/CppLibConcepts.h"

namespace Common
{
// Similar to std::bitset, this is a class which encapsulates a bitset, i.e.
@@ -29,11 +31,9 @@ namespace Common
// operation.)
// - Counting set bits using .Count() - see comment on that method.

template <typename IntTy>
template <std::unsigned_integral IntTy>
class BitSet
{
static_assert(!std::is_signed<IntTy>::value, "BitSet should not be used with signed types");

public:
// A reference to a particular bit, returned from operator[].
class Ref
@@ -11,6 +11,10 @@
#include <initializer_list>
#include <type_traits>

#include "Common/Concepts.h"

#include "Common/Future/CppLibConcepts.h"

namespace Common
{
///
@@ -113,11 +117,10 @@ constexpr Result ExtractBits(const T src) noexcept
///
/// @return A bool indicating whether the mask is valid.
///
template <typename T>
template <std::unsigned_integral T>
constexpr bool IsValidLowMask(const T mask) noexcept
{
static_assert(std::is_integral<T>::value, "Mask must be an integral type.");
static_assert(std::is_unsigned<T>::value, "Signed masks can introduce hard to find bugs.");
// Signed masks can introduce hard to find bugs.

// Can be efficiently determined without looping or bit counting. It's the counterpart
// to https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
@@ -143,38 +146,28 @@ constexpr bool IsValidLowMask(const T mask) noexcept
/// @pre Both To and From types must be the same size
/// @pre Both To and From types must satisfy the TriviallyCopyable concept.
///
template <typename To, typename From>
template <TriviallyCopyable To, TriviallyCopyable From>
inline To BitCast(const From& source) noexcept
{
static_assert(sizeof(From) == sizeof(To),
"BitCast source and destination types must be equal in size.");
static_assert(std::is_trivially_copyable<From>(),
"BitCast source type must be trivially copyable.");
static_assert(std::is_trivially_copyable<To>(),
"BitCast destination type must be trivially copyable.");

alignas(To) std::byte storage[sizeof(To)];
std::memcpy(&storage, &source, sizeof(storage));
return reinterpret_cast<To&>(storage);
}

template <typename T, typename PtrType>
template <TriviallyCopyable T, TriviallyCopyable PtrType>
class BitCastPtrType
{
public:
static_assert(std::is_trivially_copyable<PtrType>(),
"BitCastPtr source type must be trivially copyable.");
static_assert(std::is_trivially_copyable<T>(),
"BitCastPtr destination type must be trivially copyable.");

explicit BitCastPtrType(PtrType* ptr) : m_ptr(ptr) {}

// Enable operator= only for pointers to non-const data
template <typename S>
inline typename std::enable_if<std::is_same<S, T>() && !std::is_const<PtrType>()>::type
operator=(const S& source)
inline BitCastPtrType& operator=(const T& source) requires NotConst<PtrType>
{
std::memcpy(m_ptr, &source, sizeof(source));
return *this;
}

inline operator T() const
@@ -199,46 +192,34 @@ inline auto BitCastPtr(PtrType* ptr) noexcept -> BitCastPtrType<T, PtrType>
}

// Similar to BitCastPtr, but specifically for aliasing structs to arrays.
template <typename ArrayType, typename T,
typename Container = std::array<ArrayType, sizeof(T) / sizeof(ArrayType)>>
template <typename ArrayType, TriviallyCopyable T,
TriviallyCopyable Container = std::array<ArrayType, sizeof(T) / sizeof(ArrayType)>>
inline auto BitCastToArray(const T& obj) noexcept -> Container
{
static_assert(sizeof(T) % sizeof(ArrayType) == 0,
"Size of array type must be a factor of size of source type.");
static_assert(std::is_trivially_copyable<T>(),
"BitCastToArray source type must be trivially copyable.");
static_assert(std::is_trivially_copyable<Container>(),
"BitCastToArray array type must be trivially copyable.");

Container result;
std::memcpy(result.data(), &obj, sizeof(T));
return result;
}

template <typename ArrayType, typename T,
typename Container = std::array<ArrayType, sizeof(T) / sizeof(ArrayType)>>
template <typename ArrayType, TriviallyCopyable T,
TriviallyCopyable Container = std::array<ArrayType, sizeof(T) / sizeof(ArrayType)>>
inline void BitCastFromArray(const Container& array, T& obj) noexcept
{
static_assert(sizeof(T) % sizeof(ArrayType) == 0,
"Size of array type must be a factor of size of destination type.");
static_assert(std::is_trivially_copyable<Container>(),
"BitCastFromArray array type must be trivially copyable.");
static_assert(std::is_trivially_copyable<T>(),
"BitCastFromArray destination type must be trivially copyable.");

std::memcpy(&obj, array.data(), sizeof(T));
}

template <typename ArrayType, typename T,
typename Container = std::array<ArrayType, sizeof(T) / sizeof(ArrayType)>>
template <typename ArrayType, TriviallyCopyable T,
TriviallyCopyable Container = std::array<ArrayType, sizeof(T) / sizeof(ArrayType)>>
inline auto BitCastFromArray(const Container& array) noexcept -> T
{
static_assert(sizeof(T) % sizeof(ArrayType) == 0,
"Size of array type must be a factor of size of destination type.");
static_assert(std::is_trivially_copyable<Container>(),
"BitCastFromArray array type must be trivially copyable.");
static_assert(std::is_trivially_copyable<T>(),
"BitCastFromArray destination type must be trivially copyable.");

T obj;
std::memcpy(&obj, array.data(), sizeof(T));
@@ -304,11 +285,9 @@ class Flags

// Left-shift a value and set new LSBs to that of the supplied LSB.
// Converts a value from a N-bit range to an (N+X)-bit range. e.g. 0x101 -> 0x10111
template <typename T>
template <std::unsigned_integral T>
T ExpandValue(T value, size_t left_shift_amount)
{
static_assert(std::is_unsigned<T>(), "ExpandValue is only sane on unsigned types.");

return (value << left_shift_amount) |
(T(-ExtractBit<0>(value)) >> (BitSize<T>() - left_shift_amount));
}
@@ -15,6 +15,7 @@ add_library(common
CommonFuncs.h
CommonPaths.h
CommonTypes.h
Concepts.h
Config/Config.cpp
Config/Config.h
Config/ConfigInfo.cpp
@@ -58,6 +59,7 @@ add_library(common
FloatUtils.h
FormatUtil.h
FPURoundMode.h
Future/CppLibConcepts.h
GekkoDisassembler.cpp
GekkoDisassembler.h
Hash.cpp
@@ -21,14 +21,14 @@
#include <optional>
#include <set>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include <fmt/format.h>

#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/Concepts.h"
#include "Common/EnumMap.h"
#include "Common/Flag.h"
#include "Common/Inline.h"
@@ -195,13 +195,13 @@ class PointerWrap
DoArray(x.data(), static_cast<u32>(x.size()));
}

template <typename T, typename std::enable_if_t<std::is_trivially_copyable_v<T>, int> = 0>
template <Common::TriviallyCopyable T>
void DoArray(T* x, u32 count)
{
DoVoid(x, count * sizeof(T));
}

template <typename T, typename std::enable_if_t<!std::is_trivially_copyable_v<T>, int> = 0>
template <Common::NotTriviallyCopyable T>
void DoArray(T* x, u32 count)
{
for (u32 i = 0; i < count; ++i)
@@ -262,10 +262,9 @@ class PointerWrap
atomic.store(temp, std::memory_order_relaxed);
}

template <typename T>
template <Common::TriviallyCopyable T>
void Do(T& x)
{
static_assert(std::is_trivially_copyable_v<T>, "Only sane for trivially copyable types");
// Note:
// Usually we can just use x = **ptr, etc. However, this doesn't work
// for unions containing BitFields (long story, stupid language rules)
@@ -0,0 +1,68 @@
// 2022 Dolphin Emulator Project
// SPDX-License-Identifier: CC0-1.0

#pragma once

#include <cstddef>
#include <type_traits>

#include "Common/TypeUtils.h"

#include "Common/Future/CppLibConcepts.h"

namespace Common
{
template <class T>
concept TriviallyCopyable = std::is_trivially_copyable<T>::value;

template <class T>
concept NotTriviallyCopyable = !TriviallyCopyable<T>;

template <class T>
concept Const = std::is_const<T>::value;

template <class T>
concept NotConst = !Const<T>;

template <class T>
concept Enumerated = std::is_enum<T>::value;

template <class T>
concept NotEnumerated = !Enumerated<T>;

template <class T>
concept Class = std::is_class<T>::value;

template <class T>
concept Union = std::is_union<T>::value;

template <class T>
concept Arithmetic = std::is_arithmetic<T>::value;

template <class T>
concept Pointer = std::is_pointer<T>::value;

template <class T>
concept Function = std::is_function<T>::value;

template <class T>
concept FunctionPointer = Function<std::remove_pointer_t<T>>;

template <class T>
concept IntegralOrEnum = std::integral<T> || Enumerated<T>;

template <class T, class U>
concept UnderlyingSameAs = std::same_as<std::underlying_type_t<T>, U>;

template <class T, class U>
concept SameAsOrUnderlyingSameAs = std::same_as<T, U> || UnderlyingSameAs<T, U>;

template <size_t N, class... Ts>
concept CountOfTypes = IsCountOfTypes<N, Ts...>::value;

template <class T, class... Ts>
concept ConvertibleFromAllOf = IsConvertibleFromAllOf<T, Ts...>::value;

template <class T, class... Ts>
concept SameAsAnyOf = IsSameAsAnyOf<T, Ts...>::value;
}; // namespace Common
@@ -10,17 +10,11 @@
#include <utility>

#include "Common/CommonTypes.h"
#include "Common/Concepts.h"
#include "Common/Config/Enums.h"

namespace Config
{
namespace detail
{
// std::underlying_type may only be used with enum types, so make sure T is an enum type first.
template <typename T>
using UnderlyingType = typename std::enable_if_t<std::is_enum<T>{}, std::underlying_type<T>>::type;
} // namespace detail

struct Location
{
System system{};
@@ -55,12 +49,8 @@ class Info

// Make it easy to convert Info<Enum> into Info<UnderlyingType<Enum>>
// so that enum settings can still easily work with code that doesn't care about the enum values.
template <typename Enum,
std::enable_if_t<std::is_same<T, detail::UnderlyingType<Enum>>::value>* = nullptr>
Info(const Info<Enum>& other)
{
*this = other;
}
template <Common::Enumerated Enum>
requires Common::UnderlyingSameAs<Enum, T> Info(const Info<Enum>& other) { *this = other; }

Info<T>& operator=(const Info<T>& other)
{
@@ -81,9 +71,9 @@ class Info

// Make it easy to convert Info<Enum> into Info<UnderlyingType<Enum>>
// so that enum settings can still easily work with code that doesn't care about the enum values.
template <typename Enum,
std::enable_if_t<std::is_same<T, detail::UnderlyingType<Enum>>::value>* = nullptr>
Info<T>& operator=(const Info<Enum>& other)
template <Common::Enumerated Enum>
requires Common::UnderlyingSameAs<Enum, T> Info<T>
&operator=(const Info<Enum>& other)
{
m_location = other.GetLocation();
m_default_value = static_cast<T>(other.GetDefaultValue());
@@ -10,6 +10,7 @@
#include <type_traits>
#include <vector>

#include "Common/Concepts.h"
#include "Common/Config/ConfigInfo.h"
#include "Common/Config/Enums.h"
#include "Common/StringUtil.h"
@@ -18,7 +19,7 @@ namespace Config
{
namespace detail
{
template <typename T, std::enable_if_t<!std::is_enum<T>::value>* = nullptr>
template <Common::NotEnumerated T>
std::optional<T> TryParse(const std::string& str_value)
{
T value;
@@ -27,7 +28,7 @@ std::optional<T> TryParse(const std::string& str_value)
return value;
}

template <typename T, std::enable_if_t<std::is_enum<T>::value>* = nullptr>
template <Common::Enumerated T>
std::optional<T> TryParse(const std::string& str_value)
{
const auto result = TryParse<std::underlying_type_t<T>>(str_value);

0 comments on commit bfcf099

Please sign in to comment.