Replace SFINAE with concepts (#5361)

Concepts are cleaner to use and result in much better error messages for incorrect template use.
This commit is contained in:
Tyler Veness
2023-06-07 09:50:09 -07:00
committed by GitHub
parent d57d1a4598
commit 91cbcea841
42 changed files with 397 additions and 361 deletions

View File

@@ -0,0 +1,18 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <type_traits>
#include "wpi/concepts.h"
namespace wpi {
template <class Derived, class Base>
concept DecayedDerivedFrom =
std::derived_from<std::decay_t<Derived>, std::decay_t<Base>> &&
std::convertible_to<std::decay_t<Derived>*, std::decay_t<Base>*>;
} // namespace wpi

View File

@@ -9,6 +9,8 @@
#include <tuple>
#include <utility>
#include "wpi/concepts.h"
namespace wpi {
struct empty_array_t {};
@@ -26,11 +28,10 @@ class array : public std::array<T, N> {
public:
constexpr explicit array(empty_array_t) {}
template <typename... Ts>
template <std::convertible_to<T>... Ts>
requires(1 + sizeof...(Ts) == N)
constexpr array(T arg, Ts&&... args) // NOLINT
: std::array<T, N>{std::forward<T>(arg), std::forward<Ts>(args)...} {
static_assert(1 + sizeof...(args) == N, "Dimension mismatch");
}
: std::array<T, N>{std::forward<T>(arg), std::forward<Ts>(args)...} {}
constexpr array(const array<T, N>&) = default;
constexpr array& operator=(const array<T, N>&) = default;
@@ -56,33 +57,32 @@ class array : public std::array<T, N> {
}
};
template <typename T, typename... Ts>
array(T, Ts...) -> array<std::enable_if_t<(std::is_same_v<T, Ts> && ...), T>,
1 + sizeof...(Ts)>;
template <typename T, std::convertible_to<T>... Ts>
array(T, Ts...) -> array<T, 1 + sizeof...(Ts)>;
} // namespace wpi
template <size_t I, typename T, size_t N>
requires(I < N)
constexpr T& get(wpi::array<T, N>& arr) noexcept {
static_assert(I < N, "array index is within bounds");
return std::get<I>(static_cast<std::array<T, N>>(arr));
}
template <size_t I, typename T, size_t N>
requires(I < N)
constexpr T&& get(wpi::array<T, N>&& arr) noexcept {
static_assert(I < N, "array index is within bounds");
return std::move(std::get<I>(arr));
}
template <size_t I, typename T, size_t N>
requires(I < N)
constexpr const T& get(const wpi::array<T, N>& arr) noexcept {
static_assert(I < N, "array index is within bounds");
return std::get<I>(static_cast<std::array<T, N>>(arr));
}
template <size_t I, typename T, size_t N>
requires(I < N)
constexpr const T&& get(const wpi::array<T, N>&& arr) noexcept {
static_assert(I < N, "array index is within bounds");
return std::move(std::get<I>(arr));
}
@@ -94,8 +94,8 @@ struct tuple_size<wpi::array<T, N>> : public integral_constant<size_t, N> {};
// Partial specialization for wpi::array
template <size_t I, typename T, size_t N>
requires(I < N)
struct tuple_element<I, wpi::array<T, N>> {
static_assert(I < N, "index is out of bounds");
using type = T;
};
} // namespace std

View File

@@ -0,0 +1,57 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <concepts>
#if defined(__APPLE__) && !defined(__cpp_lib_concepts)
#include <functional>
#include <type_traits>
#include <utility>
namespace std {
template <typename T, typename... Args>
concept constructible_from =
is_nothrow_destructible_v<T> && is_constructible_v<T, Args...>;
template <typename From, typename To>
concept convertible_to = is_convertible_v<From, To> &&
requires { static_cast<To>(declval<From>()); };
template <typename T>
concept move_constructible = constructible_from<T, T> && convertible_to<T, T>;
template <typename T>
concept copy_constructible =
move_constructible<T> && constructible_from<T, T&> &&
convertible_to<T&, T> && constructible_from<T, const T&> &&
convertible_to<const T&, T> && constructible_from<T, const T> &&
convertible_to<const T, T>;
template <typename T>
concept default_initializable =
constructible_from<T> && requires { T{}; } && requires { ::new T; };
template <typename Derived, typename Base>
concept derived_from =
is_base_of_v<Base, Derived> &&
is_convertible_v<const volatile Derived*, const volatile Base*>;
template <typename T>
concept floating_point = is_floating_point_v<T>;
template <typename T>
concept integral = is_integral_v<T>;
template <typename F, typename... Args>
concept invocable = requires(F&& f, Args&&... args) {
invoke(forward<F>(f), forward<Args>(args)...);
};
} // namespace std
#endif // defined(__APPLE__) && !defined(__cpp_lib_concepts)

View File

@@ -11,7 +11,6 @@
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
@@ -20,6 +19,7 @@
#include "wpi/SmallString.h"
#include "wpi/SmallVector.h"
#include "wpi/StringExtras.h"
#include "wpi/concepts.h"
#include "wpi/mutex.h"
#include "wpi/raw_ostream.h"
@@ -219,9 +219,8 @@ class JArrayRefInner<C, jbyte> {
template <typename C>
class JArrayRefInner<C, jlong> {
public:
template <typename U,
typename = std::enable_if_t<sizeof(U) == sizeof(jlong) &&
std::is_integral_v<U>>>
template <typename U>
requires(sizeof(U) == sizeof(jlong) && std::integral<U>)
operator std::span<const U>() const { // NOLINT
auto arr = static_cast<const C*>(this)->array();
if (arr.empty()) {
@@ -397,46 +396,38 @@ inline jstring MakeJString(JNIEnv* env, std::string_view str) {
// details for MakeJIntArray
namespace detail {
/**
* Slow path (get primitive array and set individual elements).
*
* This is used if the input type is not an integer of the same size (note
* signed/unsigned is ignored).
*/
template <typename T,
bool = (std::is_integral<T>::value && sizeof(jint) == sizeof(T))>
template <typename T>
struct ConvertIntArray {
static jintArray ToJava(JNIEnv* env, std::span<const T> arr) {
jintArray jarr = env->NewIntArray(arr.size());
if (!jarr) {
return nullptr;
if constexpr (sizeof(T) == sizeof(jint) && std::integral<T>) {
// Fast path (use SetIntArrayRegion).
jintArray jarr = env->NewIntArray(arr.size());
if (!jarr) {
return nullptr;
}
env->SetIntArrayRegion(jarr, 0, arr.size(),
reinterpret_cast<const jint*>(arr.data()));
return jarr;
} else {
// Slow path (get primitive array and set individual elements).
//
// This is used if the input type is not an integer of the same size (note
// signed/unsigned is ignored).
jintArray jarr = env->NewIntArray(arr.size());
if (!jarr) {
return nullptr;
}
jint* elements =
static_cast<jint*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
if (!elements) {
return nullptr;
}
for (size_t i = 0; i < arr.size(); ++i) {
elements[i] = static_cast<jint>(arr[i]);
}
env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
return jarr;
}
jint* elements =
static_cast<jint*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
if (!elements) {
return nullptr;
}
for (size_t i = 0; i < arr.size(); ++i) {
elements[i] = static_cast<jint>(arr[i]);
}
env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
return jarr;
}
};
/**
* Fast path (use SetIntArrayRegion).
*/
template <typename T>
struct ConvertIntArray<T, true> {
static jintArray ToJava(JNIEnv* env, std::span<const T> arr) {
jintArray jarr = env->NewIntArray(arr.size());
if (!jarr) {
return nullptr;
}
env->SetIntArrayRegion(jarr, 0, arr.size(),
reinterpret_cast<const jint*>(arr.data()));
return jarr;
}
};
@@ -574,9 +565,9 @@ WPI_JNI_MAKEJARRAY(jdouble, Double)
#undef WPI_JNI_MAKEJARRAY
template <class T, typename = std::enable_if_t<
sizeof(typename T::value_type) == sizeof(jlong) &&
std::is_integral_v<typename T::value_type>>>
template <class T>
requires(sizeof(typename T::value_type) == sizeof(jlong) &&
std::integral<typename T::value_type>)
inline jlongArray MakeJLongArray(JNIEnv* env, const T& arr) {
jlongArray jarr = env->NewLongArray(arr.size());
if (!jarr) {

View File

@@ -10,6 +10,8 @@
#include <utility>
#include <vector>
#include "wpi/concepts.h"
namespace wpi {
/**
@@ -22,11 +24,9 @@ namespace wpi {
*/
template <typename T, typename Sequence = std::vector<T>,
typename Compare = std::less<typename Sequence::value_type>>
requires std::same_as<T, typename Sequence::value_type>
class priority_queue {
public:
static_assert(std::is_same_v<T, typename Sequence::value_type>,
"value_type must be the same as the underlying container");
using value_type = typename Sequence::value_type;
using reference = typename Sequence::reference;
using const_reference = typename Sequence::const_reference;
@@ -34,10 +34,9 @@ class priority_queue {
using container_type = Sequence;
using value_compare = Compare;
template <typename Seq = Sequence,
typename Requires = typename std::enable_if_t<
std::is_default_constructible<Compare>{} &&
std::is_default_constructible<Seq>{}>>
template <typename Seq = Sequence>
requires std::default_initializable<Compare> &&
std::default_initializable<Seq>
priority_queue() {}
priority_queue(const Compare& comp, const Sequence& c) : c(c), comp(comp) {