This is an automated email from the ASF dual-hosted git repository.
pitrou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new 6219555bb1 GH-50250: [C++] Remove `call_traits::argument_type` in
favor of `<type_traits>` (#51328)
6219555bb1 is described below
commit 6219555bb133ea57d51fd38f56079f555bd7998f
Author: Alexander Taepper <[email protected]>
AuthorDate: Wed Sep 16 19:02:20 2026 +0200
GH-50250: [C++] Remove `call_traits::argument_type` in favor of
`<type_traits>` (#51328)
### Rationale for this change
This is the last change for #50250. The remaining `FnOnce` does not have
corresponding utilities in the C++20 standard library according to my knowledge.
### What changes are included in this PR?
This removes the custom meta-programming facility
`call_traits::argument_type`. We instead use the provided `type_traits` header
which allows us to check invocability (and thus indirectly the argument type)
with e.g. `std::is_invocable`.
It needs to be mentioned that none of `std::is_invocable_v`,
`std::invoke_result_t`, .. is a drop-in replacement for the removed
`argument_type`. I instead migrated all users of the old facility to the
standard constructs. Some call-sites needed changing by supplying template
parameters explicitly, but in my opinion all changes are defendable or even
improvements.
### Are these changes tested?
Yes, this refactoring commit still passes all test cases
### Are there any user-facing changes?
No
* GitHub Issue: #50250
Authored-by: Alexander Taepper <[email protected]>
Signed-off-by: Antoine Pitrou <[email protected]>
---
cpp/src/arrow/util/bit_util_benchmark.cc | 2 +-
cpp/src/arrow/util/bitmap.h | 20 ++++++++---------
cpp/src/arrow/util/cache_benchmark.cc | 5 +++--
cpp/src/arrow/util/cache_internal.h | 37 ++++++++++++++++----------------
cpp/src/arrow/util/cache_test.cc | 14 ++++++------
cpp/src/arrow/util/functional.h | 23 --------------------
cpp/src/arrow/util/future.h | 21 ++++++------------
cpp/src/arrow/util/future_test.cc | 12 +++++------
8 files changed, 49 insertions(+), 85 deletions(-)
diff --git a/cpp/src/arrow/util/bit_util_benchmark.cc
b/cpp/src/arrow/util/bit_util_benchmark.cc
index da624bec19..bc4cdad99c 100644
--- a/cpp/src/arrow/util/bit_util_benchmark.cc
+++ b/cpp/src/arrow/util/bit_util_benchmark.cc
@@ -172,7 +172,7 @@ static void BenchmarkBitmapVisitBitsetAnd(benchmark::State&
state) {
static void BenchmarkBitmapVisitUInt8And(benchmark::State& state) {
BenchmarkAndImpl(state, [](const internal::Bitmap(&bitmaps)[2],
internal::Bitmap* out) {
int64_t i = 0;
- internal::Bitmap::VisitWords(bitmaps, [&](std::array<uint8_t, 2> uint8s) {
+ internal::Bitmap::VisitWords<uint8_t>(bitmaps, [&](std::array<uint8_t, 2>
uint8s) {
reinterpret_cast<uint8_t*>(out->mutable_data())[i++] = uint8s[0] &
uint8s[1];
});
});
diff --git a/cpp/src/arrow/util/bitmap.h b/cpp/src/arrow/util/bitmap.h
index 49f319081f..4cba55a8ad 100644
--- a/cpp/src/arrow/util/bitmap.h
+++ b/cpp/src/arrow/util/bitmap.h
@@ -21,12 +21,14 @@
#include <array>
#include <bitset>
#include <cassert>
+#include <concepts>
#include <cstdint>
#include <cstring>
#include <memory>
#include <span>
#include <string>
#include <string_view>
+#include <type_traits>
#include <utility>
#include "arrow/buffer.h"
@@ -36,7 +38,6 @@
#include "arrow/util/bitmap_writer.h"
#include "arrow/util/compare.h"
#include "arrow/util/endian.h"
-#include "arrow/util/functional.h"
#include "arrow/util/string_util.h"
#include "arrow/util/visibility.h"
@@ -145,9 +146,8 @@ class ARROW_EXPORT Bitmap : public
util::ToStringOstreamable<Bitmap>,
// carefully in other cases.
// For 2 bitmaps or less, and/or smaller bitmaps, see also
VisitTwoBitBlocksVoid
// and BitmapUInt64Reader.
- template <size_t N, typename Visitor,
- typename Word = typename std::decay<
- internal::call_traits::argument_type<0,
Visitor&&>>::type::value_type>
+ template <typename Word = uint64_t, size_t N, typename Visitor>
+ requires std::same_as<void, std::invoke_result_t<Visitor, std::array<Word,
N>&>>
static int64_t VisitWords(const Bitmap (&bitmaps_arg)[N], Visitor&& visitor)
{
constexpr int64_t kBitWidth = sizeof(Word) * 8;
@@ -248,13 +248,12 @@ class ARROW_EXPORT Bitmap : public
util::ToStringOstreamable<Bitmap>,
return min_offset;
}
- template <size_t N, size_t M, typename ReaderT, typename WriterT, typename
Visitor,
- typename Word = typename std::decay<
- internal::call_traits::argument_type<0,
Visitor&&>>::type::value_type>
+ template <size_t N, size_t M, typename ReaderT, typename WriterT, typename
Visitor>
static void RunVisitWordsAndWriteLoop(int64_t bit_length,
std::array<ReaderT, N>& readers,
std::array<WriterT, M>& writers,
Visitor&& visitor) {
+ using Word = decltype(readers[0].NextWord());
constexpr int64_t kBitWidth = sizeof(Word) * 8;
std::array<Word, N> visited_words;
@@ -318,6 +317,7 @@ class ARROW_EXPORT Bitmap : public
util::ToStringOstreamable<Bitmap>,
/// may be offset within the first visited word, but words will otherwise
contain
/// densely packed bits loaded from the bitmap. That offset within the first
word is
/// returned.
+ ///
/// Visitor is expected to have the following signature
/// [](const std::array<Word, N>& in_words, std::array<Word, M>*
out_words){...}
///
@@ -326,9 +326,9 @@ class ARROW_EXPORT Bitmap : public
util::ToStringOstreamable<Bitmap>,
// carefully in other cases.
// For 2 bitmaps or less, and/or smaller bitmaps, see also
VisitTwoBitBlocksVoid
// and BitmapUInt64Reader.
- template <size_t N, size_t M, typename Visitor,
- typename Word = typename std::decay<
- internal::call_traits::argument_type<0,
Visitor&&>>::type::value_type>
+ template <typename Word = uint64_t, size_t N, size_t M, typename Visitor>
+ requires std::same_as<
+ void, std::invoke_result_t<Visitor, std::array<Word, N>&,
std::array<Word, M>*>>
static void VisitWordsAndWrite(const std::array<Bitmap, N>& bitmaps_arg,
std::array<Bitmap, M>* out_bitmaps_arg,
Visitor&& visitor) {
diff --git a/cpp/src/arrow/util/cache_benchmark.cc
b/cpp/src/arrow/util/cache_benchmark.cc
index 7439ee2f50..071367d8ec 100644
--- a/cpp/src/arrow/util/cache_benchmark.cc
+++ b/cpp/src/arrow/util/cache_benchmark.cc
@@ -126,7 +126,7 @@ static void BenchmarkMemoize(benchmark::State& state,
Memoized&& mem,
static void MemoizeLruCached(benchmark::State& state) {
const auto keys = MakeStrings(kCacheSize, state.range(0));
const auto values = MakeStrings(kCacheSize, state.range(1));
- auto mem = MemoizeLru(Callable(values), kCacheSize);
+ auto mem = MemoizeLru<std::string>(Callable(values), kCacheSize);
BenchmarkMemoize(state, mem, keys);
}
@@ -135,7 +135,8 @@ static void MemoizeLruCachedThreadUnsafe(benchmark::State&
state) {
const auto values = MakeStrings(kCacheSize, state.range(1));
// Emulate recommended usage of MemoizeLruCachedThreadUnsafe
// (the compiler is probably able to cache the TLS-looked up value, though)
- thread_local auto mem = MemoizeLruThreadUnsafe(Callable(values), kCacheSize);
+ thread_local auto mem =
+ MemoizeLruThreadUnsafe<std::string>(Callable(values), kCacheSize);
BenchmarkMemoize(state, mem, keys);
}
diff --git a/cpp/src/arrow/util/cache_internal.h
b/cpp/src/arrow/util/cache_internal.h
index fe10af8bb2..6c65712060 100644
--- a/cpp/src/arrow/util/cache_internal.h
+++ b/cpp/src/arrow/util/cache_internal.h
@@ -26,7 +26,6 @@
#include <utility>
#include <vector>
-#include "arrow/util/functional.h"
#include "arrow/util/logging.h"
#include "arrow/util/macros.h"
@@ -167,11 +166,12 @@ struct ThreadUnsafeMemoizer {
};
template <template <typename...> class Cache, template <typename...> class
MemoizerType,
- typename Func, typename Key =
std::decay_t<call_traits::argument_type<0, Func>>,
- typename Value = std::decay_t<std::invoke_result_t<Func, const
Key&>>,
- typename Memoizer = MemoizerType<Key, Value, Cache<Key, Value>,
Func>,
- typename RetType = typename Memoizer::RetType>
-static std::function<RetType(const Key&)> Memoize(Func&& func, int32_t
cache_capacity) {
+ typename Key, typename Func>
+static auto Memoize(Func&& func, int32_t cache_capacity) {
+ using Value = std::decay_t<std::invoke_result_t<Func, const Key&>>;
+ using Memoizer = MemoizerType<Key, Value, Cache<Key, Value>, Func>;
+ using RetType = typename Memoizer::RetType;
+
// std::function<> requires copy constructibility
struct {
RetType operator()(const Key& key) const { return (*memoized_)(key); }
@@ -179,30 +179,29 @@ static std::function<RetType(const Key&)> Memoize(Func&&
func, int32_t cache_cap
} shared_memoized = {
std::make_shared<Memoizer>(std::forward<Func>(func), cache_capacity)};
- return shared_memoized;
+ return std::function<RetType(const Key&)>(std::move(shared_memoized));
}
} // namespace detail
// Apply a LRU memoization cache to a callable.
-template <typename Func>
-static auto MemoizeLru(Func&& func, int32_t cache_capacity)
- -> decltype(detail::Memoize<LruCache, detail::ThreadSafeMemoizer>(
- std::forward<Func>(func), cache_capacity)) {
- return detail::Memoize<LruCache,
detail::ThreadSafeMemoizer>(std::forward<Func>(func),
- cache_capacity);
+// `Key` is the type of the callable's (single) argument.
+template <typename Key, typename Func>
+ requires std::is_invocable_v<Func, const Key&>
+static auto MemoizeLru(Func&& func, int32_t cache_capacity) {
+ return detail::Memoize<LruCache, detail::ThreadSafeMemoizer, Key>(
+ std::forward<Func>(func), cache_capacity);
}
// Like MemoizeLru, but not thread-safe. This version allows for much faster
// lookups (more than 2x faster), but you'll have to manage thread safety
yourself.
// A recommended usage is to declare per-thread caches using `thread_local`
// (see cache_benchmark.cc).
-template <typename Func>
-static auto MemoizeLruThreadUnsafe(Func&& func, int32_t cache_capacity)
- -> decltype(detail::Memoize<LruCache, detail::ThreadUnsafeMemoizer>(
- std::forward<Func>(func), cache_capacity)) {
- return detail::Memoize<LruCache,
detail::ThreadUnsafeMemoizer>(std::forward<Func>(func),
-
cache_capacity);
+template <typename Key, typename Func>
+ requires std::is_invocable_v<Func, const Key&>
+static auto MemoizeLruThreadUnsafe(Func&& func, int32_t cache_capacity) {
+ return detail::Memoize<LruCache, detail::ThreadUnsafeMemoizer, Key>(
+ std::forward<Func>(func), cache_capacity);
}
} // namespace internal
diff --git a/cpp/src/arrow/util/cache_test.cc b/cpp/src/arrow/util/cache_test.cc
index 264bfe68ec..d009806ab3 100644
--- a/cpp/src/arrow/util/cache_test.cc
+++ b/cpp/src/arrow/util/cache_test.cc
@@ -183,18 +183,16 @@ struct Callable {
};
struct MemoizeLruFactory {
- template <typename Func,
- typename RetType = decltype(MemoizeLru(std::declval<Func>(), 0))>
- RetType operator()(Func&& func, int32_t capacity) {
- return MemoizeLru(std::forward<Func>(func), capacity);
+ template <typename Func>
+ auto operator()(Func&& func, int32_t capacity) {
+ return MemoizeLru<std::string>(std::forward<Func>(func), capacity);
}
};
struct MemoizeLruThreadUnsafeFactory {
- template <typename Func,
- typename RetType =
decltype(MemoizeLruThreadUnsafe(std::declval<Func>(), 0))>
- RetType operator()(Func&& func, int32_t capacity) {
- return MemoizeLruThreadUnsafe(std::forward<Func>(func), capacity);
+ template <typename Func>
+ auto operator()(Func&& func, int32_t capacity) {
+ return MemoizeLruThreadUnsafe<std::string>(std::forward<Func>(func),
capacity);
}
};
diff --git a/cpp/src/arrow/util/functional.h b/cpp/src/arrow/util/functional.h
index 4444b747c8..1240f6c6ca 100644
--- a/cpp/src/arrow/util/functional.h
+++ b/cpp/src/arrow/util/functional.h
@@ -18,7 +18,6 @@
#pragma once
#include <memory>
-#include <tuple>
#include <type_traits>
#include "arrow/result.h"
@@ -36,28 +35,6 @@ struct Empty {
}
};
-/// Helper struct for examining lambdas and other callables.
-/// TODO(ARROW-12655) support function pointers
-struct call_traits {
- public:
- template <std::size_t I, typename F, typename R, typename... A>
- static typename std::tuple_element<I, std::tuple<A...>>::type
argument_type_impl(
- R (F::*)(A...));
-
- template <std::size_t I, typename F, typename R, typename... A>
- static typename std::tuple_element<I, std::tuple<A...>>::type
argument_type_impl(
- R (F::*)(A...) const);
-
- template <std::size_t I, typename F, typename R, typename... A>
- static typename std::tuple_element<I, std::tuple<A...>>::type
argument_type_impl(
- R (F::*)(A...) &&);
-
- /// If F is not overloaded, the argument types of its call operator can be
- /// extracted via call_traits::argument_type<Index, F>
- template <std::size_t I, typename F>
- using argument_type =
decltype(argument_type_impl<I>(&std::decay<F>::type::operator()));
-};
-
/// A type erased callable object which may only be invoked once.
/// It can be constructed from any lambda which matches the provided call
signature.
/// Invoking it results in destruction of the lambda, freeing any
state/references
diff --git a/cpp/src/arrow/util/future.h b/cpp/src/arrow/util/future.h
index fa45427a9d..1884f59697 100644
--- a/cpp/src/arrow/util/future.h
+++ b/cpp/src/arrow/util/future.h
@@ -61,10 +61,6 @@ struct SyncType<internal::Empty> {
using type = Status;
};
-template <typename Fn>
-using first_arg_is_status =
- std::is_same<std::decay_t<internal::call_traits::argument_type<0, Fn>>,
Status>;
-
template <typename Fn, typename Then, typename Else>
using if_has_no_args = std::conditional_t<std::is_invocable_v<Fn>, Then, Else>;
@@ -446,10 +442,12 @@ class [[nodiscard]] Future {
};
};
+ // conditional whether OnComplete is invokable with Status _and not with
Result_
template <typename OnComplete>
using WrapOnComplete = typename std::conditional<
- detail::first_arg_is_status<OnComplete>::value, WrapStatusyOnComplete,
- WrapResultOnComplete>::type::template Callback<OnComplete>;
+ std::is_invocable_v<OnComplete, const Status&> &&
+ !std::is_invocable_v<OnComplete, const Result<ValueType>&>,
+ WrapStatusyOnComplete, WrapResultOnComplete>::type::template
Callback<OnComplete>;
/// \brief Consumer API: Register a callback to run when this future
completes
///
@@ -514,15 +512,8 @@ class [[nodiscard]] Future {
ContinuedFuture>::value,
"OnSuccess and OnFailure must continue with the same future
type");
- struct DummyOnSuccess {
- void operator()(const T&);
- };
- using OnSuccessArg = typename
std::decay<internal::call_traits::argument_type<
- 0, detail::if_has_no_args<OnSuccess, DummyOnSuccess,
OnSuccess>>>::type;
-
- static_assert(
- !std::is_same<OnSuccessArg, typename
EnsureResult<OnSuccessArg>::type>::value,
- "OnSuccess' argument should not be a Result");
+ static_assert(!std::is_invocable_v<OnSuccess, const Result<T>&>,
+ "OnSuccess' argument should not be a Result");
void operator()(const Result<T>& result) && {
detail::ContinueFuture continue_future;
diff --git a/cpp/src/arrow/util/future_test.cc
b/cpp/src/arrow/util/future_test.cc
index 2ed2b69aed..1b55ddc6d8 100644
--- a/cpp/src/arrow/util/future_test.cc
+++ b/cpp/src/arrow/util/future_test.cc
@@ -1539,13 +1539,11 @@ TEST(FnOnceTest, MoveOnlyDataType) {
return *i0.data + *i1.data + (i0.moves * 1000) + (i1.moves * 100);
};
- using arg0 = call_traits::argument_type<0, decltype(fn)>;
- using arg1 = call_traits::argument_type<1, decltype(fn)>;
- using arg2 = call_traits::argument_type<2, decltype(fn)>;
- static_assert(std::is_same<arg0, const MoveOnlyDataType&>::value, "");
- static_assert(std::is_same<arg1, MoveOnlyDataType>::value, "");
- static_assert(std::is_same<arg2, std::string>::value,
- "should not add a && to the call type (demanding rvalue
unnecessarily)");
+ static_assert(std::is_invocable_r_v<int, decltype(fn), const
MoveOnlyDataType&,
+ MoveOnlyDataType, std::string&>);
+ // a move-only by-value argument must be moved in
+ static_assert(!std::is_invocable_v<decltype(fn), const MoveOnlyDataType&,
+ MoveOnlyDataType&, std::string&>);
MoveOnlyDataType i0{1}, i1{41};
std::string copyable = "";