Update clang-tidy. (#10730)

- Install cmake using pip.
- Fix compile command generation.
- Clean up the tidy script and remove the need to load the yaml file.
- Fix modernized type traits.
- Fix span class. Polymorphism support is dropped
This commit is contained in:
Jiaming Yuan
2024-08-22 04:12:18 +08:00
committed by GitHub
parent 03bd1183bc
commit cb54374550
34 changed files with 361 additions and 387 deletions

View File

@@ -82,7 +82,7 @@ class AllreduceFunctor {
}
private:
template <class T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
template <class T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
void AccumulateBitwise(T* buffer, T const* input, std::size_t size, Op reduce_operation) const {
switch (reduce_operation) {
case Op::kBitwiseAND:
@@ -99,7 +99,7 @@ class AllreduceFunctor {
}
}
template <class T, std::enable_if_t<std::is_floating_point<T>::value>* = nullptr>
template <class T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
void AccumulateBitwise(T*, T const*, std::size_t, Op) const {
LOG(FATAL) << "Floating point types do not support bitwise operations.";
}

View File

@@ -81,8 +81,8 @@ struct AtomicDispatcher<sizeof(uint64_t)> {
// atomicAdd is not defined for size_t.
template <typename T = size_t,
std::enable_if_t<std::is_same<size_t, T>::value &&
!std::is_same<size_t, unsigned long long>::value> * = // NOLINT
std::enable_if_t<std::is_same_v<size_t, T> &&
!std::is_same_v<size_t, unsigned long long>> * = // NOLINT
nullptr>
XGBOOST_DEV_INLINE T atomicAdd(T *addr, T v) { // NOLINT
using Type = typename dh::detail::AtomicDispatcher<sizeof(T)>::Type;
@@ -381,7 +381,7 @@ void CopyTo(Src const &src, Dst *dst) {
dst->resize(src.size());
using SVT = std::remove_cv_t<typename Src::value_type>;
using DVT = std::remove_cv_t<typename Dst::value_type>;
static_assert(std::is_same<SVT, DVT>::value,
static_assert(std::is_same_v<SVT, DVT>,
"Host and device containers must have same value type.");
dh::safe_cuda(cudaMemcpyAsync(thrust::raw_pointer_cast(dst->data()), src.data(),
src.size() * sizeof(SVT), cudaMemcpyDefault));

View File

@@ -224,11 +224,11 @@ void JsonArray::Save(JsonWriter* writer) const { writer->Visit(this); }
namespace {
// error C2668: 'fpclassify': ambiguous call to overloaded function
template <typename T>
std::enable_if_t<std::is_floating_point<T>::value, bool> IsInfMSVCWar(T v) {
std::enable_if_t<std::is_floating_point_v<T>, bool> IsInfMSVCWar(T v) {
return std::isinf(v);
}
template <typename T>
std::enable_if_t<std::is_integral<T>::value, bool> IsInfMSVCWar(T) {
std::enable_if_t<std::is_integral_v<T>, bool> IsInfMSVCWar(T) {
return false;
}
} // namespace
@@ -247,7 +247,7 @@ bool JsonTypedArray<T, kind>::operator==(Value const& rhs) const {
if (vec_.size() != arr.size()) {
return false;
}
if (std::is_same<float, T>::value) {
if (std::is_same_v<float, T>) {
for (size_t i = 0; i < vec_.size(); ++i) {
bool equal{false};
if (common::CheckNAN(vec_[i])) {
@@ -693,10 +693,10 @@ void Json::Dump(Json json, JsonWriter* writer) {
writer->Save(json);
}
static_assert(std::is_nothrow_move_constructible<Json>::value);
static_assert(std::is_nothrow_move_constructible<Object>::value);
static_assert(std::is_nothrow_move_constructible<Array>::value);
static_assert(std::is_nothrow_move_constructible<String>::value);
static_assert(std::is_nothrow_move_constructible_v<Json>);
static_assert(std::is_nothrow_move_constructible_v<Object>);
static_assert(std::is_nothrow_move_constructible_v<Array>);
static_assert(std::is_nothrow_move_constructible_v<String>);
Json UBJReader::ParseArray() {
auto marker = PeekNextChar();
@@ -887,17 +887,17 @@ template <typename T, Value::ValueKind kind>
void WriteTypedArray(JsonTypedArray<T, kind> const* arr, std::vector<char>* stream) {
stream->emplace_back('[');
stream->push_back('$');
if (std::is_same<T, float>::value) {
if (std::is_same_v<T, float>) {
stream->push_back('d');
} else if (std::is_same_v<T, double>) {
stream->push_back('D');
} else if (std::is_same<T, int8_t>::value) {
} else if (std::is_same_v<T, int8_t>) {
stream->push_back('i');
} else if (std::is_same<T, uint8_t>::value) {
} else if (std::is_same_v<T, uint8_t>) {
stream->push_back('U');
} else if (std::is_same<T, int32_t>::value) {
} else if (std::is_same_v<T, int32_t>) {
stream->push_back('l');
} else if (std::is_same<T, int64_t>::value) {
} else if (std::is_same_v<T, int64_t>) {
stream->push_back('L');
} else {
LOG(FATAL) << "Not implemented";

View File

@@ -12,7 +12,7 @@
#include <algorithm> // for max
#include <cmath> // for exp, abs, log, lgamma
#include <limits> // for numeric_limits
#include <type_traits> // for is_floating_point, conditional, is_signed, is_same, declval, enable_if
#include <type_traits> // for is_floating_point_v, conditional, is_signed, is_same, declval
#include <utility> // for pair
namespace xgboost {
@@ -43,15 +43,11 @@ XGBOOST_DEVICE inline double Sigmoid(double x) {
*/
template <typename T, typename U>
XGBOOST_DEVICE constexpr bool CloseTo(T a, U b) {
using Casted =
typename std::conditional<
std::is_floating_point<T>::value || std::is_floating_point<U>::value,
double,
typename std::conditional<
std::is_signed<T>::value || std::is_signed<U>::value,
int64_t,
uint64_t>::type>::type;
return std::is_floating_point<Casted>::value ?
using Casted = typename std::conditional_t<
std::is_floating_point_v<T> || std::is_floating_point_v<U>, double,
typename std::conditional_t<std::is_signed_v<T> || std::is_signed_v<U>, std::int64_t,
std::uint64_t>>;
return std::is_floating_point_v<Casted> ?
std::abs(static_cast<Casted>(a) -static_cast<Casted>(b)) < 1e-6 : a == b;
}
@@ -65,11 +61,10 @@ XGBOOST_DEVICE constexpr bool CloseTo(T a, U b) {
*/
template <typename Iterator>
XGBOOST_DEVICE inline void Softmax(Iterator start, Iterator end) {
static_assert(std::is_same<bst_float,
typename std::remove_reference<
decltype(std::declval<Iterator>().operator*())>::type
>::value,
"Values should be of type bst_float");
static_assert(
std::is_same_v<
float, typename std::remove_reference_t<decltype(std::declval<Iterator>().operator*())>>,
"Values should be of type bst_float");
bst_float wmax = *start;
for (Iterator i = start+1; i != end; ++i) {
wmax = fmaxf(*i, wmax);
@@ -137,9 +132,7 @@ inline float LogSum(Iterator begin, Iterator end) {
// Redefined here to workaround a VC bug that doesn't support overloading for integer
// types.
template <typename T>
XGBOOST_DEVICE typename std::enable_if<
std::numeric_limits<T>::is_integer, bool>::type
CheckNAN(T) {
XGBOOST_DEVICE typename std::enable_if_t<std::numeric_limits<T>::is_integer, bool> CheckNAN(T) {
return false;
}

View File

@@ -1,9 +1,9 @@
/*!
* Copyright 2022 by XGBoost Contributors
/**
* Copyright 2022-2024, XGBoost Contributors
*/
#include "numeric.h"
#include <type_traits> // std::is_same
#include <type_traits> // std::is_same_v
#include "xgboost/context.h" // Context
#include "xgboost/host_device_vector.h" // HostDeviceVector
@@ -16,7 +16,7 @@ double Reduce(Context const* ctx, HostDeviceVector<float> const& values) {
} else {
auto const& h_values = values.ConstHostVector();
auto result = cpu_impl::Reduce(ctx, h_values.cbegin(), h_values.cend(), 0.0);
static_assert(std::is_same<decltype(result), double>::value);
static_assert(std::is_same_v<decltype(result), double>);
return result;
}
}

View File

@@ -1,17 +1,18 @@
/**
* Copyright 2022-2023 by XGBoost contributors.
* Copyright 2022-2024, XGBoost contributors.
*/
#ifndef XGBOOST_COMMON_NUMERIC_H_
#define XGBOOST_COMMON_NUMERIC_H_
#include <dmlc/common.h> // OMPException
#include <algorithm> // for std::max
#include <cstddef> // for size_t
#include <cstdint> // for int32_t
#include <iterator> // for iterator_traits
#include <numeric> // for accumulate
#include <vector>
#include <algorithm> // for max
#include <cstddef> // for size_t
#include <cstdint> // for int32_t
#include <iterator> // for iterator_traits
#include <numeric> // for accumulate
#include <type_traits> // for is_same_v
#include <vector> // for vector
#include "common.h" // AssertGPUSupport
#include "threading_utils.h" // MemStackAllocator, DefaultMaxThreads
@@ -44,8 +45,8 @@ void RunLengthEncode(Iter begin, Iter end, std::vector<Idx>* p_out) {
*/
template <typename InIt, typename OutIt, typename T>
void PartialSum(int32_t n_threads, InIt begin, InIt end, T init, OutIt out_it) {
static_assert(std::is_same<T, typename std::iterator_traits<InIt>::value_type>::value);
static_assert(std::is_same<T, typename std::iterator_traits<OutIt>::value_type>::value);
static_assert(std::is_same_v<T, typename std::iterator_traits<InIt>::value_type>);
static_assert(std::is_same_v<T, typename std::iterator_traits<OutIt>::value_type>);
// The number of threads is pegged to the batch size. If the OMP block is parallelized
// on anything other than the batch/block size, it should be reassigned
auto n = static_cast<size_t>(std::distance(begin, end));

View File

@@ -105,9 +105,9 @@ class TrainingObserver {
/*\brief Observe objects with `XGBoostParamer' type. */
template <typename Parameter,
typename std::enable_if<
std::is_base_of<XGBoostParameter<Parameter>, Parameter>::value>::type* = nullptr>
void Observe(const Parameter &p, std::string name) const {
typename std::enable_if_t<std::is_base_of_v<XGBoostParameter<Parameter>, Parameter>>* =
nullptr>
void Observe(const Parameter& p, std::string name) const {
if (XGBOOST_EXPECT(!kObserve, true)) { return; }
Json obj {toJson(p)};

View File

@@ -8,8 +8,9 @@
#include <thrust/transform_scan.h>
#include <thrust/unique.h>
#include <limits> // std::numeric_limits
#include <numeric> // for partial_sum
#include <limits> // for numeric_limits
#include <numeric> // for partial_sum
#include <type_traits> // for is_same_v
#include <utility>
#include "../collective/allgather.h"
@@ -108,7 +109,7 @@ void PruneImpl(common::Span<SketchContainer::OffsetT const> cuts_ptr,
template <typename T, typename U>
void CopyTo(Span<T> out, Span<U> src) {
CHECK_EQ(out.size(), src.size());
static_assert(std::is_same<std::remove_cv_t<T>, std::remove_cv_t<T>>::value);
static_assert(std::is_same_v<std::remove_cv_t<T>, std::remove_cv_t<T>>);
dh::safe_cuda(cudaMemcpyAsync(out.data(), src.data(),
out.size_bytes(),
cudaMemcpyDefault));

View File

@@ -15,7 +15,7 @@
#include <cstddef> // std::size_t
#include <iterator> // std::distance
#include <limits> // std::numeric_limits
#include <type_traits> // std::is_floating_point,std::iterator_traits
#include <type_traits> // std::is_floating_point_v,std::iterator_traits
#include "algorithm.cuh" // SegmentedArgMergeSort
#include "cuda_context.cuh" // CUDAContext
@@ -37,9 +37,9 @@ struct QuantileSegmentOp {
AlphaIt alpha_it;
Span<float> d_results;
static_assert(std::is_floating_point<typename std::iterator_traits<ValIt>::value_type>::value,
static_assert(std::is_floating_point_v<typename std::iterator_traits<ValIt>::value_type>,
"Invalid value for quantile.");
static_assert(std::is_floating_point<typename std::iterator_traits<ValIt>::value_type>::value,
static_assert(std::is_floating_point_v<typename std::iterator_traits<ValIt>::value_type>,
"Invalid alpha.");
XGBOOST_DEVICE void operator()(std::size_t seg_idx) {
@@ -102,9 +102,9 @@ struct WeightedQuantileSegOp {
Span<float const> d_weight_cdf;
Span<std::size_t const> d_sorted_idx;
Span<float> d_results;
static_assert(std::is_floating_point<typename std::iterator_traits<AlphaIt>::value_type>::value,
static_assert(std::is_floating_point_v<typename std::iterator_traits<AlphaIt>::value_type>,
"Invalid alpha.");
static_assert(std::is_floating_point<typename std::iterator_traits<ValIt>::value_type>::value,
static_assert(std::is_floating_point_v<typename std::iterator_traits<ValIt>::value_type>,
"Invalid value for quantile.");
XGBOOST_DEVICE void operator()(std::size_t seg_idx) {
@@ -146,7 +146,7 @@ auto MakeWQSegOp(SegIt seg_it, ValIt val_it, AlphaIt alpha_it, Span<float const>
* std::distance(seg_begin, seg_end) should be equal to n_segments + 1
*/
template <typename SegIt, typename ValIt, typename AlphaIt,
std::enable_if_t<!std::is_floating_point<AlphaIt>::value>* = nullptr>
std::enable_if_t<!std::is_floating_point_v<AlphaIt>>* = nullptr>
void SegmentedQuantile(Context const* ctx, AlphaIt alpha_it, SegIt seg_begin, SegIt seg_end,
ValIt val_begin, ValIt val_end, HostDeviceVector<float>* quantiles) {
dh::device_vector<std::size_t> sorted_idx;
@@ -197,8 +197,8 @@ void SegmentedQuantile(Context const* ctx, double alpha, SegIt seg_begin, SegIt
* @param w_begin Iterator for weight for each input element
*/
template <typename SegIt, typename ValIt, typename AlphaIt, typename WIter,
typename std::enable_if_t<!std::is_same<
typename std::iterator_traits<AlphaIt>::value_type, void>::value>* = nullptr>
typename std::enable_if_t<
!std::is_same_v<typename std::iterator_traits<AlphaIt>::value_type, void>>* = nullptr>
void SegmentedWeightedQuantile(Context const* ctx, AlphaIt alpha_it, SegIt seg_beg, SegIt seg_end,
ValIt val_begin, ValIt val_end, WIter w_begin, WIter w_end,
HostDeviceVector<float>* quantiles) {

View File

@@ -49,7 +49,7 @@ float Quantile(Context const* ctx, double alpha, Iter const& begin, Iter const&
}
auto val = [&](size_t i) { return *(begin + sorted_idx[i]); };
static_assert(std::is_same<decltype(val(0)), float>::value);
static_assert(std::is_same_v<decltype(val(0)), float>);
if (alpha <= (1 / (n + 1))) {
return val(0);

View File

@@ -128,7 +128,7 @@ class Transform {
}
#if defined(__CUDACC__)
template <typename std::enable_if<CompiledWithCuda>::type* = nullptr,
template <typename std::enable_if_t<CompiledWithCuda>* = nullptr,
typename... HDV>
void LaunchCUDA(Functor _func, HDV*... _vectors) const {
UnpackShard(device_, _vectors...);
@@ -151,9 +151,8 @@ class Transform {
}
#else
/*! \brief Dummy function defined when compiling for CPU. */
template <typename std::enable_if<!CompiledWithCuda>::type* = nullptr,
typename... HDV>
void LaunchCUDA(Functor _func, HDV*...) const {
template <typename std::enable_if_t<!CompiledWithCuda> * = nullptr, typename... HDV>
void LaunchCUDA(Functor _func, HDV *...) const {
// Remove unused parameter compiler warning.
(void) _func;

View File

@@ -12,7 +12,7 @@
#include <limits> // for numeric_limits
#include <map> // for map
#include <string> // for string
#include <type_traits> // for alignment_of, remove_pointer_t, invoke_result_t
#include <type_traits> // for alignment_of_v, remove_pointer_t, invoke_result_t
#include <vector> // for vector
#include "../common/bitfield.h" // for RBitField8
@@ -334,7 +334,7 @@ struct ToDType<double> {
};
template <typename T>
struct ToDType<T,
std::enable_if_t<std::is_same<T, long double>::value && sizeof(long double) == 16>> {
std::enable_if_t<std::is_same_v<T, long double> && sizeof(long double) == 16>> {
static constexpr ArrayInterfaceHandler::Type kType = ArrayInterfaceHandler::kF16;
};
// uint
@@ -555,7 +555,7 @@ class ArrayInterface {
}
[[nodiscard]] XGBOOST_DEVICE std::size_t ElementAlignment() const {
return this->DispatchCall([](auto *typed_data_ptr) {
return std::alignment_of<std::remove_pointer_t<decltype(typed_data_ptr)>>::value;
return std::alignment_of_v<std::remove_pointer_t<decltype(typed_data_ptr)>>;
});
}
@@ -567,9 +567,8 @@ class ArrayInterface {
#if defined(XGBOOST_USE_CUDA)
// No operator defined for half -> size_t
using Type = std::conditional_t<
std::is_same<__half,
std::remove_cv_t<std::remove_pointer_t<decltype(p_values)>>>::value &&
std::is_same<std::size_t, std::remove_cv_t<T>>::value,
std::is_same_v<__half, std::remove_cv_t<std::remove_pointer_t<decltype(p_values)>>> &&
std::is_same_v<std::size_t, std::remove_cv_t<T>>,
unsigned long long, T>; // NOLINT
return static_cast<T>(static_cast<Type>(p_values[offset]));
#else

View File

@@ -294,16 +294,14 @@ SimpleDMatrix::SimpleDMatrix(AdapterT* adapter, float missing, int nthread,
IteratorAdapter<DataIterHandle, XGBCallbackDataIterNext, XGBoostBatchCSR>;
// If AdapterT is either IteratorAdapter or FileAdapter type, use the total batch size to
// determine the correct number of rows, as offset_vec may be too short
if (std::is_same<AdapterT, IteratorAdapterT>::value ||
std::is_same<AdapterT, FileAdapter>::value) {
if (std::is_same_v<AdapterT, IteratorAdapterT> || std::is_same_v<AdapterT, FileAdapter>) {
info_.num_row_ = total_batch_size;
// Ensure offset_vec.size() - 1 == [number of rows]
while (offset_vec.size() - 1 < total_batch_size) {
offset_vec.emplace_back(offset_vec.back());
}
} else {
CHECK((std::is_same<AdapterT, CSCAdapter>::value ||
std::is_same<AdapterT, CSCArrayAdapter>::value))
CHECK((std::is_same_v<AdapterT, CSCAdapter> || std::is_same_v<AdapterT, CSCArrayAdapter>))
<< "Expecting CSCAdapter";
info_.num_row_ = offset_vec.size() - 1;
}

View File

@@ -344,7 +344,7 @@ class LambdaRankNDCG : public LambdaRankObj<LambdaRankNDCG, ltr::NDCGCache> {
common::Span<double const> discount, bst_group_t g) {
auto delta = [&](auto y_high, auto y_low, std::size_t rank_high, std::size_t rank_low,
bst_group_t g) {
static_assert(std::is_floating_point<decltype(y_high)>::value);
static_assert(std::is_floating_point_v<decltype(y_high)>);
return DeltaNDCG<exp_gain>(y_high, y_low, rank_high, rank_low, inv_IDCG(g), discount);
};
this->CalcLambdaForGroup<unbiased>(iter, g_predt, g_label, w, g_rank, g, delta, g_gpair);

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019-2023, XGBoost contributors
* Copyright 2019-2024, XGBoost contributors
*/
#include <thrust/copy.h>
#include <thrust/device_vector.h>

View File

@@ -41,7 +41,7 @@ XGBOOST_DEVICE float LossChangeMissing(const GradientPairInt64 &scan,
template <int kBlockSize>
class EvaluateSplitAgent {
public:
using ArgMaxT = cub::KeyValuePair<int, float>;
using ArgMaxT = cub::KeyValuePair<std::uint32_t, float>;
using BlockScanT = cub::BlockScan<GradientPairInt64, kBlockSize>;
using MaxReduceT = cub::WarpReduce<ArgMaxT>;
using SumReduceT = cub::WarpReduce<GradientPairInt64>;

View File

@@ -1,10 +1,10 @@
/**
* Copyright 2023 by XGBoost Contributors
* Copyright 2023-2024, XGBoost Contributors
*/
#ifndef XGBOOST_TREE_IO_UTILS_H_
#define XGBOOST_TREE_IO_UTILS_H_
#include <string> // for string
#include <type_traits> // for enable_if_t, is_same, conditional_t
#include <type_traits> // for enable_if_t, is_same_v, conditional_t
#include <vector> // for vector
#include "xgboost/json.h" // for Json
@@ -23,26 +23,24 @@ using IndexArrayT = std::conditional_t<feature_is_64, I64ArrayT<typed>, I32Array
// typed array, not boolean
template <typename JT, typename T>
std::enable_if_t<!std::is_same<T, Json>::value && !std::is_same<JT, Boolean>::value, T> GetElem(
std::enable_if_t<!std::is_same_v<T, Json> && !std::is_same_v<JT, Boolean>, T> GetElem(
std::vector<T> const& arr, size_t i) {
return arr[i];
}
// typed array boolean
template <typename JT, typename T>
std::enable_if_t<!std::is_same<T, Json>::value && std::is_same<T, uint8_t>::value &&
std::is_same<JT, Boolean>::value,
bool>
std::enable_if_t<
!std::is_same_v<T, Json> && std::is_same_v<T, uint8_t> && std::is_same_v<JT, Boolean>, bool>
GetElem(std::vector<T> const& arr, size_t i) {
return arr[i] == 1;
}
// json array
template <typename JT, typename T>
std::enable_if_t<
std::is_same<T, Json>::value,
std::conditional_t<std::is_same<JT, Integer>::value, int64_t,
std::conditional_t<std::is_same<Boolean, JT>::value, bool, float>>>
std::enable_if_t<std::is_same_v<T, Json>,
std::conditional_t<std::is_same_v<JT, Integer>, int64_t,
std::conditional_t<std::is_same_v<Boolean, JT>, bool, float>>>
GetElem(std::vector<T> const& arr, size_t i) {
if (std::is_same<JT, Boolean>::value && !IsA<Boolean>(arr[i])) {
if (std::is_same_v<JT, Boolean> && !IsA<Boolean>(arr[i])) {
return get<Integer const>(arr[i]) == 1;
}
return get<JT const>(arr[i]);

View File

@@ -12,7 +12,7 @@
#include <iomanip>
#include <limits>
#include <sstream>
#include <type_traits>
#include <type_traits> // for is_floating_point_v
#include "../common/categorical.h" // for GetNodeCats
#include "../common/common.h" // for EscapeU8
@@ -35,7 +35,7 @@ namespace {
template <typename Float>
std::enable_if_t<std::is_floating_point_v<Float>, std::string> ToStr(Float value) {
int32_t constexpr kFloatMaxPrecision = std::numeric_limits<float>::max_digits10;
static_assert(std::is_floating_point<Float>::value,
static_assert(std::is_floating_point_v<Float>,
"Use std::to_string instead for non-floating point values.");
std::stringstream ss;
ss << std::setprecision(kFloatMaxPrecision) << value;
@@ -45,7 +45,7 @@ std::enable_if_t<std::is_floating_point_v<Float>, std::string> ToStr(Float value
template <typename Float>
std::string ToStr(linalg::VectorView<Float> value, bst_target_t limit) {
int32_t constexpr kFloatMaxPrecision = std::numeric_limits<float>::max_digits10;
static_assert(std::is_floating_point<Float>::value,
static_assert(std::is_floating_point_v<Float>,
"Use std::to_string instead for non-floating point values.");
std::stringstream ss;
ss << std::setprecision(kFloatMaxPrecision);
@@ -1091,8 +1091,8 @@ void LoadModelImpl(Json const& in, TreeParam const& param, std::vector<RTreeNode
stats = std::remove_reference_t<decltype(stats)>(n_nodes);
nodes = std::remove_reference_t<decltype(nodes)>(n_nodes);
static_assert(std::is_integral<decltype(GetElem<Integer>(lefts, 0))>::value);
static_assert(std::is_floating_point<decltype(GetElem<Number>(loss_changes, 0))>::value);
static_assert(std::is_integral_v<decltype(GetElem<Integer>(lefts, 0))>);
static_assert(std::is_floating_point_v<decltype(GetElem<Number>(loss_changes, 0))>);
// Set node
for (int32_t i = 0; i < n_nodes; ++i) {