这是我对std::midpoint的概括,其中包含了为求两个值的平均值收到的建议。除了像std::midpoint一样支持算术类型和指针之外,它还支持迭代器、复数和用户定义的类型,如bignum、理性主义和不动点数。
除了第一次评审的建议外,我还做了一些其他的修改:
bool作为参数类型std::ptrdiff_t来说太大了:如果数组太大(大于PTRDIFF_MAX元素,但小于SIZE_MAX字节),那么两个指针之间的差异可能不能表示为std::ptrdiff_t,那么减去两个这样的指针的结果是没有定义的。- 优先选择 (CC-BY-SA3.0)#include <array>
#include <cmath>
#include <complex>
#include <concepts>
#include <cstddef>
#include <iterator>
#include <type_traits>
#include <utility>
namespace toby
{
namespace detail
{
// A point on an affine line can be compared with another, and has
// a difference type (which may be the same type).
template<typename T>
concept affine_point = std::regular<T> && requires(T a, T b) {
a < b;
a + (b - a);
};
// Subtraction helper, for pedantic handling of very large arrays
template<typename T>
auto distance(const T& a, const T& b) {
return b - a;
}
template<typename T>
requires ( sizeof (T) < SIZE_MAX / PTRDIFF_MAX )
std::size_t distance(const T* a, const T* b) {
if (b < a) {
std::swap(a, b);
}
// If an array has more than PTRDIFF_MAX elements,
// subtraction is undefined if the result is not
// representable as std::ptrdiff_t.
std::size_t gap = 1;
while (a + gap < b - gap) {
gap *= 2;
}
// (b - gap - a) promotes to size_t if necessary
return b - gap - a + gap;
}
void midpoint(bool, bool) = delete;
template<affine_point T>
constexpr T midpoint(const T& a, const T& b)
{
if (a == b) {
// This ensures infinities are correctly returned.
return a;
}
if constexpr (std::is_signed_v<T>) {
if ((a < 0) != (b < 0)) {
// Values are opposite sign; avoid overflow when
// magnitudes are large.
return (a + b) / 2;
}
}
if (a < b) {
return a + distance(a, b) / 2;
} else {
return b + distance(b, a) / 2;
}
}
// Iterators
// If not random-access, then a MUST be before b
template<std::input_or_output_iterator Iter, std::sentinel_for<Iter> S>
constexpr Iter midpoint(Iter a, const S& b)
{
std::ranges::advance(a, std::ranges::distance(a, b) / 2);
return a;
}
// Aggregate types follow
// Pattern can be extended, e.g. for popular geometry types
template<affine_point T>
constexpr std::complex<T> midpoint(const std::complex<T>& a, const std::complex<T>& b)
{
return {
midpoint(a.real(), b.real()),
midpoint(a.imag(), b.imag())
};
}
template<affine_point T, std::size_t N>
constexpr std::array<T,N> midpoint(const std::array<T,N>& a, const std::array<T,N>& b)
{
std::array<T,N> result;
auto f = [](auto&& x, auto&& y) { return midpoint(x,y); };
std::transform(a.begin(), a.end(), b.begin(), result.begin(), f);
return result;
}
}
using detail::midpoint;
}#include <gtest/gtest.h>
using toby::midpoint;
#include <climits>
TEST(midpoint, int)
{
EXPECT_EQ(midpoint(0, 0), 0);
EXPECT_EQ(midpoint(0, 1), 0);
EXPECT_EQ(midpoint(0, 2), 1);
EXPECT_EQ(midpoint(1, 3), 2);
EXPECT_EQ(midpoint(4, 1), 2);
EXPECT_EQ(midpoint(INT_MIN, 0), INT_MIN/2);
EXPECT_EQ(midpoint(INT_MAX, 0), INT_MAX/2);
EXPECT_EQ(midpoint(INT_MAX, -INT_MAX), 0);
}
#include <limits>
TEST(midpoint, double)
{
static constexpr auto inf = std::numeric_limits<double>::infinity();
static constexpr auto nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ(midpoint(0.0, 0.0), 0.0);
EXPECT_EQ(midpoint(1.0, 2.0), 1.5);
EXPECT_EQ(midpoint(1.0, inf), inf);
EXPECT_EQ(midpoint(1.0, -inf), -inf);
EXPECT_EQ(midpoint(inf, inf), inf);
EXPECT_EQ(midpoint(-inf, -inf), -inf);
EXPECT_TRUE(std::isnan(midpoint(inf, -inf)));
EXPECT_TRUE(std::isnan(midpoint(nan, 0.0)));
EXPECT_TRUE(std::isnan(midpoint(0.0, nan)));
EXPECT_TRUE(std::isnan(midpoint(nan, nan)));
}
#include <complex>
TEST(midpoint, complex)
{
auto const a = std::complex{2,10};
auto const b = std::complex{0,20};
auto const c = std::complex{1,15};
EXPECT_EQ(midpoint(a, b), c);
}
TEST(midpoint, pointer)
{
char const s[50] = {};
EXPECT_EQ(midpoint(s+1, s+25), s+13);
EXPECT_EQ(midpoint(s+25, s+1), s+13);
}
#include <string_view>
TEST(midpoint, iterator)
{
auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
EXPECT_EQ(*midpoint(s.begin(), s.end()), 'n');
EXPECT_EQ(*midpoint(s.end(), s.begin()), 'n');
}
#include <list>
TEST(midpoint, bidi_iterator)
{
auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
auto const l = std::list(s.begin(), s.end());
EXPECT_EQ(*midpoint(l.begin(), l.end()), 'n');
}
#include <forward_list>
TEST(midpoint, forward_iterator)
{
auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
auto const l = std::forward_list(s.begin(), s.end());
EXPECT_EQ(*midpoint(l.begin(), l.end()), 'n');
}
#include <array>
TEST(midpoint, std_array)
{
auto const a = std::array{ 0, 10, 20};
auto const b = std::array{10, 10, 10};
auto const c = std::array{ 5, 10, 15};
EXPECT_EQ(midpoint(a, b), c);
}发布于 2021-06-10 08:23:01
std::transform是用<algorithm>定义的。你应该把这个包括进去。template <std::ranges::range range>
constexpr auto midpoint(range &&r)
{
return midpoint(std::ranges::begin(r), std::ranges::end(r));
}发布于 2021-06-13 20:21:19
最后版本,包含了其他答案的建议。
作出的改动:
is_detected_v实现,因为我的平台还没有标准的)。std::midpoint()处理指针类型。bool而不是重载,因此显式专门化会受到影响。std::forward_iterator概念,禁止单程迭代器.#include <algorithm>
#include <array>
#include <cmath>
#include <complex>
#include <concepts>
#include <cstddef>
#include <iterator>
#include <numeric>
#include <ranges>
#include <type_traits>
#include <utility>
namespace toby
{
namespace detail
{
// A point on an affine line can be compared with another, and has
// a difference type (which may be the same type).
template<typename T>
concept affine_point
= std::regular<T>
&& !std::is_same_v<std::decay_t<T>, bool>
&& requires(T a, T b) { a < b; a + (b - a); };
template<typename T>
constexpr auto midpoint(const T* a, const T* b) {
return std::midpoint(a, b);
}
template<affine_point T>
constexpr T midpoint(const T& a, const T& b)
{
if (a == b) {
// This ensures infinities are correctly returned.
return a;
}
if constexpr (std::is_signed_v<T>) {
if ((a < 0) != (b < 0)) {
// Values are opposite sign; avoid overflow when
// magnitudes are large.
return (a + b) / 2;
}
}
if (a < b) {
return a + (b - a) / 2;
} else {
return b + (a - b) / 2;
}
}
// Iterators and ranges
// If not random-access, then a MUST precede b
template<std::forward_iterator Iter, std::sentinel_for<Iter> S>
constexpr Iter midpoint(Iter a, const S& b)
{
std::ranges::advance(a, std::ranges::distance(a, b) / 2);
return a;
}
template <std::ranges::range Range>
constexpr auto midpoint(const Range& r)
{
return midpoint(std::ranges::begin(r), std::ranges::end(r));
}
// Aggregate types follow
// Pattern can be extended, e.g. for popular geometry types
template<affine_point T>
constexpr std::complex<T> midpoint(const std::complex<T>& a, const std::complex<T>& b)
{
return {
midpoint(a.real(), b.real()),
midpoint(a.imag(), b.imag())
};
}
template<affine_point T, std::size_t N>
constexpr std::array<T,N> midpoint(const std::array<T,N>& a, const std::array<T,N>& b)
{
std::array<T,N> result;
auto f = [](auto&& x, auto&& y) { return midpoint(x,y); };
std::transform(a.begin(), a.end(), b.begin(), result.begin(), f);
return result;
}
}
using detail::midpoint;
}#include <gtest/gtest.h>
using toby::midpoint;
#include <climits>
TEST(midpoint, int)
{
EXPECT_EQ(midpoint(0, 0), 0);
EXPECT_EQ(midpoint(0, 1), 0);
EXPECT_EQ(midpoint(0, 2), 1);
EXPECT_EQ(midpoint(1, 3), 2);
EXPECT_EQ(midpoint(4, 1), 2);
EXPECT_EQ(midpoint(INT_MIN, 0), INT_MIN/2);
EXPECT_EQ(midpoint(INT_MAX, 0), INT_MAX/2);
EXPECT_EQ(midpoint(INT_MAX, -INT_MAX), 0);
}
#include <limits>
TEST(midpoint, double)
{
static constexpr auto inf = std::numeric_limits<double>::infinity();
static constexpr auto nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ(midpoint(0.0, 0.0), 0.0);
EXPECT_EQ(midpoint(1.0, 2.0), 1.5);
EXPECT_EQ(midpoint(1.0, inf), inf);
EXPECT_EQ(midpoint(1.0, -inf), -inf);
EXPECT_EQ(midpoint(inf, inf), inf);
EXPECT_EQ(midpoint(-inf, -inf), -inf);
EXPECT_TRUE(std::isnan(midpoint(inf, -inf)));
EXPECT_TRUE(std::isnan(midpoint(nan, 0.0)));
EXPECT_TRUE(std::isnan(midpoint(0.0, nan)));
EXPECT_TRUE(std::isnan(midpoint(nan, nan)));
}
#include <complex>
TEST(midpoint, complex)
{
auto const a = std::complex{2,10};
auto const b = std::complex{0,20};
auto const c = std::complex{1,15};
EXPECT_EQ(midpoint(a, b), c);
}
TEST(midpoint, pointer)
{
char const s[50] = {};
EXPECT_EQ(midpoint(s+1, s+25), s+13);
EXPECT_EQ(midpoint(s+25, s+1), s+13);
}
#include <string_view>
TEST(midpoint, iterator)
{
auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
EXPECT_EQ(*midpoint(s.begin(), s.end()), 'n');
EXPECT_EQ(*midpoint(s.end(), s.begin()), 'n');
EXPECT_EQ(*midpoint(s), 'n');
}
#include <list>
TEST(midpoint, bidi_iterator)
{
auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
auto const l = std::list(s.begin(), s.end());
EXPECT_EQ(*midpoint(l.begin(), l.end()), 'n');
EXPECT_EQ(*midpoint(l), 'n');
}
#include <forward_list>
TEST(midpoint, forward_iterator)
{
auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
auto const l = std::forward_list(s.begin(), s.end());
EXPECT_EQ(*midpoint(l.begin(), l.end()), 'n');
EXPECT_EQ(*midpoint(l), 'n');
}
#include <array>
TEST(midpoint, std_array)
{
auto const a = std::array{ 0, 10, 20};
auto const b = std::array{10, 10, 10};
auto const c = std::array{ 5, 10, 15};
EXPECT_EQ(midpoint(a, b), c);
}
#if 1
// use detection idiom to ensure invalid overloads don't exist
// Inspired from library fundamentals TS v3
namespace {
template <class AlwaysVoid, template<class...> class Op, class... Args>
struct detector : std::false_type {};
template <template<class...> class Op, class... Args>
struct detector<std::void_t<Op<Args...>>, Op, Args...> : std::true_type {};
template <template<class...> class Op, class... Args>
constexpr bool is_detected_v = detector<void, Op, Args...>::value;
}
#else
#include <experimental/type_traits>
using std::is_detected_v;
#endif
template<typename T, typename U = T>
using midpoint_exists = decltype(midpoint(std::declval<T>(), std::declval<U>()));
//static constexpr bool a = is_detected<midpoint_exists, int, int>;
static_assert(is_detected_v<midpoint_exists, int>);
static_assert(!is_detected_v<midpoint_exists, bool>);
static_assert(!is_detected_v<midpoint_exists, bool&>);
static_assert(!is_detected_v<midpoint_exists, std::istream_iterator<char>>);https://codereview.stackexchange.com/questions/262873
复制相似问题