55 | Topology::Topology(Taskflow& tf, P&& p, C&& c):
56 | _taskflow(tf),
57 | _pred {std::forward(p)},
58 | _call {std::forward(c)} {
59 | }
60 |
61 | } // end of namespace tf. ----------------------------------------------------
62 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/core/worker.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "declarations.hpp"
4 | #include "tsq.hpp"
5 | #include "notifier.hpp"
6 |
7 | /**
8 | @file worker.hpp
9 | @brief worker include file
10 | */
11 |
12 | namespace tf {
13 |
14 | /**
15 | @private
16 | */
17 | struct Worker {
18 |
19 | friend class Executor;
20 | friend class WorkerView;
21 |
22 | private:
23 |
24 | size_t _id;
25 | size_t _vtm;
26 | Executor* _executor;
27 | Notifier::Waiter* _waiter;
28 | std::default_random_engine _rdgen { std::random_device{}() };
29 | TaskQueue _wsq;
30 | };
31 |
32 | // ----------------------------------------------------------------------------
33 | // Class Definition: WorkerView
34 | // ----------------------------------------------------------------------------
35 |
36 | /**
37 | @class WorkerView
38 |
39 | @brief class to create an immutable view of a worker in an executor
40 |
41 | An executor keeps a set of internal worker threads to run tasks.
42 | A worker view provides users an immutable interface to observe
43 | when a worker runs a task, and the view object is only accessible
44 | from an observer derived from tf::ObserverInterface.
45 | */
46 | class WorkerView {
47 |
48 | friend class Executor;
49 |
50 | public:
51 |
52 | /**
53 | @brief queries the worker id associated with the executor
54 |
55 | A worker id is a unsigned integer in the range [0, N),
56 | where @c N is the number of workers spawned at the construction
57 | time of the executor.
58 | */
59 | size_t id() const;
60 |
61 | /**
62 | @brief queries the size of the queue (i.e., number of pending tasks to
63 | run) associated with the worker
64 | */
65 | size_t queue_size() const;
66 |
67 | /**
68 | @brief queries the current capacity of the queue
69 | */
70 | size_t queue_capacity() const;
71 |
72 | private:
73 |
74 | WorkerView(const Worker&);
75 | WorkerView(const WorkerView&) = default;
76 |
77 | const Worker& _worker;
78 |
79 | };
80 |
81 | // Constructor
82 | inline WorkerView::WorkerView(const Worker& w) : _worker{w} {
83 | }
84 |
85 | // function: id
86 | inline size_t WorkerView::id() const {
87 | return _worker._id;
88 | }
89 |
90 | // Function: queue_size
91 | inline size_t WorkerView::queue_size() const {
92 | return _worker._wsq.size();
93 | }
94 |
95 | // Function: queue_capacity
96 | inline size_t WorkerView::queue_capacity() const {
97 | return static_cast(_worker._wsq.capacity());
98 | }
99 |
100 |
101 | } // end of namespact tf -----------------------------------------------------
102 |
103 |
104 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/cublasflow.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // taskflow.hpp
4 | // ^
5 | // |
6 | // cudaflow.hpp
7 | // ^
8 | // |
9 | // cublasflow.hpp
10 |
11 | #include "cudaflow.hpp"
12 |
13 | #include "cuda/cublas/cublas_flow.hpp"
14 | #include "cuda/cublas/cublas_helper.hpp"
15 | #include "cuda/cublas/cublas_level1.hpp"
16 | #include "cuda/cublas/cublas_level2.hpp"
17 | #include "cuda/cublas/cublas_level3.hpp"
18 |
19 | /**
20 | @file cublasflow.hpp
21 | @brief main cublasFlow include file
22 | */
23 |
24 |
25 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/cuda/cublas/cublas_error.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace tf {
6 |
7 | // cuBLAS API errors
8 | constexpr const char* cublas_error_to_string(cublasStatus_t error) {
9 | switch (error) {
10 | case CUBLAS_STATUS_SUCCESS:
11 | return "CUBLAS_STATUS_SUCCESS";
12 |
13 | case CUBLAS_STATUS_NOT_INITIALIZED:
14 | return "CUBLAS_STATUS_NOT_INITIALIZED";
15 |
16 | case CUBLAS_STATUS_ALLOC_FAILED:
17 | return "CUBLAS_STATUS_ALLOC_FAILED";
18 |
19 | case CUBLAS_STATUS_INVALID_VALUE:
20 | return "CUBLAS_STATUS_INVALID_VALUE";
21 |
22 | case CUBLAS_STATUS_ARCH_MISMATCH:
23 | return "CUBLAS_STATUS_ARCH_MISMATCH";
24 |
25 | case CUBLAS_STATUS_MAPPING_ERROR:
26 | return "CUBLAS_STATUS_MAPPING_ERROR";
27 |
28 | case CUBLAS_STATUS_EXECUTION_FAILED:
29 | return "CUBLAS_STATUS_EXECUTION_FAILED";
30 |
31 | case CUBLAS_STATUS_INTERNAL_ERROR:
32 | return "CUBLAS_STATUS_INTERNAL_ERROR";
33 |
34 | case CUBLAS_STATUS_NOT_SUPPORTED:
35 | return "CUBLAS_STATUS_NOT_SUPPORTED";
36 |
37 | case CUBLAS_STATUS_LICENSE_ERROR:
38 | return "CUBLAS_STATUS_LICENSE_ERROR";
39 | }
40 |
41 | return "unknown cublas error";
42 | }
43 |
44 |
45 |
46 | #define TF_CHECK_CUBLAS(...) \
47 | if(TF_CUDA_GET_FIRST(__VA_ARGS__) != CUBLAS_STATUS_SUCCESS) { \
48 | std::ostringstream oss; \
49 | auto ev = TF_CUDA_GET_FIRST(__VA_ARGS__); \
50 | auto error_str = cublas_error_to_string(ev); \
51 | oss << "[" << __FILE__ << ":" << __LINE__ << " " \
52 | << error_str << "] "; \
53 | tf::ostreamize(oss, TF_CUDA_REMOVE_FIRST(__VA_ARGS__)); \
54 | throw std::runtime_error(oss.str()); \
55 | }
56 |
57 |
58 | } // end of namespace tf -----------------------------------------------------
59 |
60 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/cuda/cublas/cublas_helper.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "cublas_handle.hpp"
4 |
5 | namespace tf {
6 |
7 | // ----------------------------------------------------------------------------
8 | // global utility functions
9 | // ----------------------------------------------------------------------------
10 | // find the tranposed op
11 | template && std::is_same_v, void>* = nullptr
13 | >
14 | constexpr cublasOperation_t cublas_rtran(cublasOperation_t op) {
15 | if(op != CUBLAS_OP_N && op != CUBLAS_OP_T) {
16 | TF_THROW("invalid transposition op for floating data types");
17 | }
18 | return (op == CUBLAS_OP_N) ? CUBLAS_OP_T : CUBLAS_OP_N;
19 | }
20 |
21 | // find the transposed fill
22 | constexpr cublasFillMode_t cublas_rfill(cublasFillMode_t uplo) {
23 | switch(uplo) {
24 | case CUBLAS_FILL_MODE_LOWER: return CUBLAS_FILL_MODE_UPPER;
25 | case CUBLAS_FILL_MODE_UPPER: return CUBLAS_FILL_MODE_LOWER;
26 | default: return uplo;
27 | }
28 | }
29 |
30 | // find the transposed side
31 | constexpr cublasSideMode_t cublas_rside(cublasSideMode_t side) {
32 | switch(side) {
33 | case CUBLAS_SIDE_LEFT : return CUBLAS_SIDE_RIGHT;
34 | case CUBLAS_SIDE_RIGHT: return CUBLAS_SIDE_LEFT;
35 | default: return side;
36 | }
37 | }
38 |
39 | // ----------------------------------------------------------------------------
40 | // cublasFlowCapturer helper functions
41 | // ----------------------------------------------------------------------------
42 |
43 | // Function: vset
44 | template , void>*
46 | >
47 | cudaTask cublasFlowCapturer::vset(
48 | size_t n, const T* h, int inch, T* d, int incd
49 | ) {
50 | return factory()->on([n, h, inch, d, incd] (cudaStream_t stream) mutable {
51 | TF_CHECK_CUBLAS(
52 | cublasSetVectorAsync(n, sizeof(T), h, inch, d, incd, stream),
53 | "failed to run vset_async"
54 | );
55 | });
56 | }
57 |
58 | // Function: vget
59 | template , void>*
61 | >
62 | cudaTask cublasFlowCapturer::vget(size_t n, const T* d, int incd, T* h, int inch) {
63 | return factory()->on([n, d, incd, h, inch] (cudaStream_t stream) mutable {
64 | TF_CHECK_CUBLAS(
65 | cublasGetVectorAsync(n, sizeof(T), d, incd, h, inch, stream),
66 | "failed to run vget_async"
67 | );
68 | });
69 | }
70 |
71 | } // end of namespace tf -----------------------------------------------------
72 |
73 |
74 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/cuda/cuda_algorithm/cuda_matmul.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "../cuda_error.hpp"
4 |
5 | namespace tf {
6 |
7 | // ----------------------------------------------------------------------------
8 | // row-major matrix multiplication
9 | // ----------------------------------------------------------------------------
10 |
11 | template
12 | __global__ void cuda_matmul(
13 | const T* A,
14 | const T* B,
15 | T* C,
16 | size_t M,
17 | size_t K,
18 | size_t N
19 | ) {
20 | __shared__ T A_tile[32][32];
21 | __shared__ T B_tile[32][32];
22 |
23 | size_t x = blockIdx.x * blockDim.x + threadIdx.x;
24 | size_t y = blockIdx.y * blockDim.y + threadIdx.y;
25 |
26 | T res = 0;
27 |
28 | for(size_t k = 0; k < K; k += 32) {
29 | if((threadIdx.x + k) < K && y < M) {
30 | A_tile[threadIdx.y][threadIdx.x] = A[y * K + threadIdx.x + k];
31 | }
32 | else{
33 | A_tile[threadIdx.y][threadIdx.x] = 0;
34 | }
35 |
36 | if((threadIdx.y + k) < K && x < N) {
37 | B_tile[threadIdx.y][threadIdx.x] = B[(threadIdx.y + k) * N + x];
38 | }
39 | else{
40 | B_tile[threadIdx.y][threadIdx.x] = 0;
41 | }
42 |
43 | __syncthreads();
44 |
45 | for(size_t i = 0; i < 32; ++i) {
46 | res += A_tile[threadIdx.y][i] * B_tile[i][threadIdx.x];
47 | }
48 | __syncthreads();
49 | }
50 |
51 | if(x < N && y < M) {
52 | C[y * N + x] = res;
53 | }
54 |
55 | }
56 |
57 | } // end of namespace tf ---------------------------------------------------------
58 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/cuda/cuda_algorithm/cuda_transform.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "../cuda_flow.hpp"
4 | #include "../cuda_capturer.hpp"
5 |
6 | namespace tf {
7 |
8 | // ----------------------------------------------------------------------------
9 | // transform
10 | // ----------------------------------------------------------------------------
11 |
12 | // Kernel: for_each
13 | template
14 | __global__ void cuda_transform(I first, size_t N, F op, S... srcs) {
15 | size_t i = blockIdx.x*blockDim.x + threadIdx.x;
16 | if (i < N) {
17 | //data[i] = op(src[i]...);
18 | *(first + i) = op((*(srcs+i))...);
19 | }
20 | }
21 |
22 | // ----------------------------------------------------------------------------
23 | // cudaFlow
24 | // ----------------------------------------------------------------------------
25 |
26 | // Function: transform
27 | template
28 | cudaTask cudaFlow::transform(I first, I last, C&& c, S... srcs) {
29 |
30 | // TODO: special case when N is 0?
31 |
32 | size_t N = std::distance(first, last);
33 | size_t B = _default_block_size(N);
34 |
35 | return kernel(
36 | (N+B-1) / B, B, 0, cuda_transform,
37 | first, N, std::forward(c), srcs...
38 | );
39 | }
40 |
41 | // Procedure: update_transform
42 | template
43 | void cudaFlow::update_transform(
44 | cudaTask task, I first, I last, C&& c, S... srcs
45 | ) {
46 |
47 | // TODO: special case when N is 0?
48 | size_t N = std::distance(first, last);
49 | size_t B = _default_block_size(N);
50 |
51 | update_kernel(
52 | task, (N+B-1) / B, B, 0, first, N, std::forward(c), srcs...
53 | );
54 | }
55 |
56 | // ----------------------------------------------------------------------------
57 | // cudaFlowCapturer
58 | // ----------------------------------------------------------------------------
59 |
60 | // Function: transform
61 | template
62 | cudaTask cudaFlowCapturer::transform(I first, I last, C&& c, S... srcs) {
63 |
64 | // TODO: special case when N is 0?
65 | size_t N = std::distance(first, last);
66 | size_t B = _default_block_size(N);
67 |
68 | return on([=, c=std::forward(c)]
69 | (cudaStream_t stream) mutable {
70 | cuda_transform<<<(N+B-1)/B, B, 0, stream>>>(first, N, c, srcs...);
71 | });
72 | }
73 |
74 | // Function: rebind_transform
75 | template
76 | void cudaFlowCapturer::rebind_transform(
77 | cudaTask task, I first, I last, C&& c, S... srcs
78 | ) {
79 |
80 | // TODO: special case when N is 0?
81 | size_t N = std::distance(first, last);
82 | size_t B = _default_block_size(N);
83 |
84 | rebind_on(task, [=, c=std::forward(c)]
85 | (cudaStream_t stream) mutable {
86 | cuda_transform<<<(N+B-1)/B, B, 0, stream>>>(first, N, c, srcs...);
87 | });
88 | }
89 |
90 | } // end of namespace tf -----------------------------------------------------
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/cuda/cuda_algorithm/cuda_transpose.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "../cuda_error.hpp"
4 |
5 | namespace tf {
6 |
7 | // ----------------------------------------------------------------------------
8 | // row-wise matrix transpose
9 | // ----------------------------------------------------------------------------
10 | //
11 | template
12 | __global__ void cuda_transpose(
13 | const T* d_in,
14 | T* d_out,
15 | size_t rows,
16 | size_t cols
17 | ) {
18 | __shared__ T tile[32][32];
19 | size_t x = blockIdx.x * 32 + threadIdx.x;
20 | size_t y = blockIdx.y * 32 + threadIdx.y;
21 |
22 | for(size_t i = 0; i < 32; i += 8) {
23 | if(x < cols && (y + i) < rows) {
24 | tile[threadIdx.y + i][threadIdx.x] = d_in[(y + i) * cols + x];
25 | }
26 | }
27 |
28 | __syncthreads();
29 |
30 | x = blockIdx.y * 32 + threadIdx.x;
31 | y = blockIdx.x * 32 + threadIdx.y;
32 |
33 | for(size_t i = 0; i < 32; i += 8) {
34 | if(x < rows && (y + i) < cols) {
35 | d_out[(y + i) * rows + x] = tile[threadIdx.x][threadIdx.y + i];
36 | }
37 | }
38 | }
39 |
40 | } // end of namespace --------------------------------------------------------
41 |
42 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/cuda/cuda_error.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include "../utility/stream.hpp"
9 |
10 | #define TF_CUDA_EXPAND( x ) x
11 | #define TF_CUDA_REMOVE_FIRST_HELPER(N, ...) __VA_ARGS__
12 | #define TF_CUDA_REMOVE_FIRST(...) TF_CUDA_EXPAND(TF_CUDA_REMOVE_FIRST_HELPER(__VA_ARGS__))
13 | #define TF_CUDA_GET_FIRST_HELPER(N, ...) N
14 | #define TF_CUDA_GET_FIRST(...) TF_CUDA_EXPAND(TF_CUDA_GET_FIRST_HELPER(__VA_ARGS__))
15 |
16 | #define TF_CHECK_CUDA(...) \
17 | if(TF_CUDA_GET_FIRST(__VA_ARGS__) != cudaSuccess) { \
18 | std::ostringstream oss; \
19 | auto ev = TF_CUDA_GET_FIRST(__VA_ARGS__); \
20 | oss << "[" << __FILE__ << ":" << __LINE__ << "] " \
21 | << (cudaGetErrorString(ev)) << " (" \
22 | << (cudaGetErrorName(ev)) << ") -"; \
23 | tf::ostreamize(oss, TF_CUDA_REMOVE_FIRST(__VA_ARGS__)); \
24 | throw std::runtime_error(oss.str()); \
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/cudaflow.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // taskflow.hpp
4 | // ^
5 | // |
6 | // cudaflow.hpp
7 |
8 | #include "taskflow.hpp"
9 | #include "cuda/cuda_flow.hpp"
10 | #include "cuda/cuda_algorithm/cuda_for_each.hpp"
11 | #include "cuda/cuda_algorithm/cuda_transform.hpp"
12 | #include "cuda/cuda_algorithm/cuda_reduce.hpp"
13 |
14 | /**
15 | @file cudaflow.hpp
16 | @brief main cudaFlow include file
17 | */
18 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/dsl/connection.hpp:
--------------------------------------------------------------------------------
1 | // 2020/08/28 - Created by netcan: https://github.com/netcan
2 | #pragma once
3 | #include "../core/flow_builder.hpp"
4 | #include "task_trait.hpp"
5 | #include "tuple_utils.hpp"
6 | #include "type_list.hpp"
7 |
8 | namespace tf {
9 | namespace dsl {
10 | template class Connection {
11 | using FROMs = typename TaskTrait::TaskList;
12 | using TOs = typename TaskTrait::TaskList;
13 |
14 | public:
15 | using FromTaskList = Unique_t>;
16 | using ToTaskList = Unique_t>;
17 | };
18 |
19 | template > struct Chain;
20 |
21 | template struct Chainvoid, OUT> {
22 | using From = F;
23 | using type = OUT;
24 | };
25 |
26 | template
27 | struct ChainT, OUT> {
28 | private:
29 | using To = typename Chain::From;
30 |
31 | public:
32 | using From = F;
33 | using type = typename Chain<
34 | T, typename OUT::template appendTo>>::type;
35 | };
36 |
37 | template struct OneToOneLink {
38 | template struct InstanceType {
39 | constexpr void build(TasksCB &tasksCb) {
40 | constexpr size_t TasksCBSize = std::tuple_size::value;
41 | constexpr size_t FromTaskIndex =
42 | TupleElementByF_v::template apply>;
43 | constexpr size_t ToTaskIndex =
44 | TupleElementByF_v::template apply>;
45 | static_assert(FromTaskIndex < TasksCBSize && ToTaskIndex < TasksCBSize,
46 | "fatal: not find TaskCb in TasksCB");
47 | std::get(tasksCb).task_.precede(
48 | std::get(tasksCb).task_);
49 | }
50 | };
51 | };
52 | } // namespace dsl
53 | }; // namespace tf
54 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/dsl/dsl.hpp:
--------------------------------------------------------------------------------
1 | // TaskflowDSL is an experimental project that leverages C++17 to
2 | // provide a dedicated interface for expressive taskflow programming
3 | //
4 | // Created by netcan: https://github.com/netcan
5 |
6 | #pragma once
7 |
8 | #include "dsl/task_dsl.hpp"
9 |
10 | namespace tf {
11 |
12 |
13 | } // end of namespace tf -----------------------------------------------------
14 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/dsl/task_analyzer.hpp:
--------------------------------------------------------------------------------
1 | // 2020/08/28 - Created by netcan: https://github.com/netcan
2 | #pragma once
3 | #include "connection.hpp"
4 | #include "type_list.hpp"
5 | #include
6 |
7 | namespace tf {
8 | namespace dsl {
9 | template class TaskAnalyzer {
10 | template
11 | struct BuildOneToOneLink;
12 |
13 | template
14 | struct BuildOneToOneLink, Ts> {
15 | using type = Concat_t::type...>;
16 | };
17 |
18 | template
19 | struct BuildOneToOneLink,
20 | std::enable_if_t>> {
21 | using type = TypeList...>;
22 | };
23 |
24 | template class OneToOneLinkSetF {
25 | using FromTaskList = typename Link::FromTaskList;
26 | using ToTaskList = typename Link::ToTaskList;
27 |
28 | public:
29 | using type = typename BuildOneToOneLink::type;
30 | };
31 |
32 | public:
33 | using AllTasks = Unique_t<
34 | Concat_t>;
35 | using OneToOneLinkSet =
36 | Unique_t, OneToOneLinkSetF>>>;
37 | };
38 |
39 | } // namespace dsl
40 | } // namespace tf
41 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/dsl/task_trait.hpp:
--------------------------------------------------------------------------------
1 | // 2020/08/28 - Created by netcan: https://github.com/netcan
2 | #pragma once
3 | #include "../core/flow_builder.hpp"
4 | #include "../core/task.hpp"
5 | #include "type_list.hpp"
6 | #include
7 |
8 | namespace tf {
9 | namespace dsl {
10 | struct TaskSignature {};
11 |
12 | template struct TaskCb {
13 | using TaskType = TASK;
14 | void build(FlowBuilder &build, const CONTEXT &context) {
15 | task_ = build.emplace(TaskType{context}());
16 | }
17 |
18 | Task task_;
19 | };
20 |
21 | template struct IsTask {
22 | template struct apply {
23 | constexpr static bool value =
24 | std::is_same::value;
25 | };
26 | };
27 |
28 | template struct TaskTrait;
29 |
30 | template struct SomeTask {
31 | using TaskList =
32 | Unique_t::TaskList...>>>;
33 | };
34 |
35 | // a task self
36 | template
37 | struct TaskTrait<
38 | TASK, std::enable_if_t::value>> {
39 | using TaskList = TypeList;
40 | };
41 |
42 | template struct TaskTrait> {
43 | using TaskList = typename SomeTask::TaskList;
44 | };
45 | } // namespace dsl
46 | } // namespace tf
47 |
--------------------------------------------------------------------------------
/thirdparty/taskflow/taskflow/dsl/tuple_utils.hpp:
--------------------------------------------------------------------------------
1 | // 2020/08/28 - Created by netcan: https://github.com/netcan
2 | #pragma once
3 | #include
4 | #include
5 |
6 | namespace tf {
7 | namespace dsl {
8 | namespace detail {
9 | // get tuple element index by f, if not exists then index >= tuple_size
10 | template class F, typename = void>
11 | struct TupleElementByF {
12 | constexpr static size_t Index = 0;
13 | };
14 |
15 | template class F, typename H, typename... Ts>
16 | struct TupleElementByF, F, std::enable_if_t::value>> {
17 | constexpr static size_t Index = 0;
18 | };
19 |
20 | template class F, typename H, typename... Ts>
21 | struct TupleElementByF, F,
22 | std::enable_if_t::value>> {
23 | constexpr static size_t Index =
24 | 1 + TupleElementByF, F>::Index;
25 | };
26 |
27 | template
28 | constexpr inline T AggregationByTupImpl(TUP &&tup, std::index_sequence) {
29 | return T{std::get(tup)...};
30 | }
31 | } // namespace detail
32 |
33 | template class F>
34 | constexpr size_t TupleElementByF_v = detail::TupleElementByF::Index;
35 |
36 | template