mirror of
https://gitlab.com/libeigen/eigen.git
synced 2026-04-10 11:34:33 +08:00
Clean up informal language, vague TODOs, and dead code in comments
libeigen/eigen!2191 Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
This commit is contained in:
@@ -178,9 +178,6 @@ class ArrayBase : public DenseBase<Derived> {
|
||||
return MatrixWrapper<const Derived>(derived());
|
||||
}
|
||||
|
||||
// template<typename Dest>
|
||||
// inline void evalTo(Dest& dst) const { dst = matrix(); }
|
||||
|
||||
protected:
|
||||
EIGEN_DEFAULT_COPY_CONSTRUCTOR(ArrayBase)
|
||||
EIGEN_DEFAULT_EMPTY_CONSTRUCTOR_AND_DESTRUCTOR(ArrayBase)
|
||||
|
||||
@@ -63,7 +63,7 @@ struct copy_using_evaluator_traits {
|
||||
static constexpr int RestrictedLinearSize = min_size_prefer_fixed(MaxSizeAtCompileTime, MaxPacketSize);
|
||||
static constexpr int OuterStride = outer_stride_at_compile_time<Dst>::ret;
|
||||
|
||||
// TODO distinguish between linear traversal and inner-traversals
|
||||
// TODO: distinguish between linear traversal and inner-traversal packet types.
|
||||
using LinearPacketType = typename find_best_packet<DstScalar, RestrictedLinearSize>::type;
|
||||
using InnerPacketType = typename find_best_packet<DstScalar, RestrictedInnerSize>::type;
|
||||
|
||||
@@ -1016,7 +1016,7 @@ struct Assignment<DstXprType, CwiseNullaryOp<scalar_zero_op<typename DstXprType:
|
||||
};
|
||||
|
||||
// Generic assignment through evalTo.
|
||||
// TODO: not sure we have to keep that one, but it helps porting current code to new evaluator mechanism.
|
||||
// TODO: evaluate whether this generic evalTo-based assignment path is still needed.
|
||||
// Note that the last template argument "Weak" is needed to make it possible to perform
|
||||
// both partial specialization+SFINAE without ambiguous specialization
|
||||
template <typename DstXprType, typename SrcXprType, typename Functor, typename Weak>
|
||||
|
||||
@@ -98,7 +98,7 @@ class CwiseBinaryOp : public CwiseBinaryOpImpl<BinaryOp, LhsType, RhsType,
|
||||
typedef std::remove_reference_t<RhsNested> RhsNested_;
|
||||
|
||||
#if EIGEN_COMP_MSVC
|
||||
// Required for Visual Studio or the Copy constructor will probably not get inlined!
|
||||
// Required for Visual Studio, which may fail to inline the copy constructor otherwise.
|
||||
EIGEN_STRONG_INLINE CwiseBinaryOp(const CwiseBinaryOp<BinaryOp, LhsType, RhsType>&) = default;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -431,8 +431,7 @@ class DenseBase
|
||||
|
||||
// By default, the fastest version with undefined NaN propagation semantics is
|
||||
// used.
|
||||
// TODO(rmlarsen): Replace with default template argument when we move to
|
||||
// c++11 or beyond.
|
||||
// TODO(rmlarsen): Replace with default template argument (C++14 is now the minimum standard).
|
||||
EIGEN_DEVICE_FUNC inline typename internal::traits<Derived>::Scalar minCoeff() const {
|
||||
return minCoeff<PropagateFast>();
|
||||
}
|
||||
@@ -449,7 +448,7 @@ class DenseBase
|
||||
template <int NaNPropagation, typename IndexType>
|
||||
EIGEN_DEVICE_FUNC typename internal::traits<Derived>::Scalar maxCoeff(IndexType* index) const;
|
||||
|
||||
// TODO(rmlarsen): Replace these methods with a default template argument.
|
||||
// TODO(rmlarsen): Replace these methods with a default template argument (C++14 is now the minimum standard).
|
||||
template <typename IndexType>
|
||||
EIGEN_DEVICE_FUNC inline typename internal::traits<Derived>::Scalar minCoeff(IndexType* row, IndexType* col) const {
|
||||
return minCoeff<PropagateFast>(row, col);
|
||||
@@ -580,12 +579,12 @@ class DenseBase
|
||||
#else
|
||||
typedef std::conditional_t<(Flags & DirectAccessBit) == DirectAccessBit,
|
||||
internal::pointer_based_stl_iterator<Derived>,
|
||||
internal::generic_randaccess_stl_iterator<Derived> >
|
||||
internal::generic_randaccess_stl_iterator<Derived>>
|
||||
iterator_type;
|
||||
|
||||
typedef std::conditional_t<(Flags & DirectAccessBit) == DirectAccessBit,
|
||||
internal::pointer_based_stl_iterator<const Derived>,
|
||||
internal::generic_randaccess_stl_iterator<const Derived> >
|
||||
internal::generic_randaccess_stl_iterator<const Derived>>
|
||||
const_iterator_type;
|
||||
|
||||
// Stl-style iterators are supported only for vectors.
|
||||
|
||||
@@ -89,7 +89,7 @@ struct product_type {
|
||||
/* The following allows to select the kind of product at compile time
|
||||
* based on the three dimensions of the product.
|
||||
* This is a compile time mapping from {1,Small,Large}^3 -> {product types} */
|
||||
// FIXME I'm not sure the current mapping is the ideal one.
|
||||
// FIXME: the current compile-time product-type mapping may not be optimal.
|
||||
template <int M, int N>
|
||||
struct product_type_selector<M, N, 1> {
|
||||
enum { ret = OuterProduct };
|
||||
@@ -193,12 +193,11 @@ struct product_type_selector<Large, Large, Small> {
|
||||
* Implementation of Inner Vector Vector Product
|
||||
***********************************************************************/
|
||||
|
||||
// FIXME : maybe the "inner product" could return a Scalar
|
||||
// instead of a 1x1 matrix ??
|
||||
// Pro: more natural for the user
|
||||
// Cons: this could be a problem if in a meta unrolled algorithm a matrix-matrix
|
||||
// product ends up to a row-vector times col-vector product... To tackle this use
|
||||
// case, we could have a specialization for Block<MatrixType,1,1> with: operator=(Scalar x);
|
||||
// FIXME: consider returning a Scalar instead of a 1x1 matrix for inner products.
|
||||
// Pro: more natural for the user.
|
||||
// Con: in a meta-unrolled algorithm a matrix-matrix product may reduce to a
|
||||
// row-vector times column-vector product. To handle this, we could specialize
|
||||
// Block<MatrixType,1,1> with operator=(Scalar x).
|
||||
|
||||
/***********************************************************************
|
||||
* Implementation of Outer Vector Vector Product
|
||||
|
||||
@@ -1329,9 +1329,7 @@ EIGEN_DEVICE_FUNC inline typename unpacket_traits<Packet>::type predux_max(const
|
||||
/** \internal \returns true if all coeffs of \a a means "true"
|
||||
* It is supposed to be called on values returned by pcmp_*.
|
||||
*/
|
||||
// not needed yet
|
||||
// template<typename Packet> EIGEN_DEVICE_FUNC inline bool predux_all(const Packet& a)
|
||||
// { return bool(a); }
|
||||
// TODO: implement predux_all when needed.
|
||||
|
||||
/** \internal \returns true if any coeffs of \a a means "true"
|
||||
* It is supposed to be called on values returned by pcmp_*.
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#ifndef EIGEN_MATHFUNCTIONS_H
|
||||
#define EIGEN_MATHFUNCTIONS_H
|
||||
|
||||
// TODO this should better be moved to NumTraits
|
||||
// TODO: consider moving these constants to NumTraits.
|
||||
// Source: WolframAlpha
|
||||
#define EIGEN_PI 3.141592653589793238462643383279502884197169399375105820974944592307816406L
|
||||
#define EIGEN_LOG2E 1.442695040888963407359924681001892137426645954152985934135449406931109219L
|
||||
@@ -390,7 +390,7 @@ struct cast_impl<OldType, NewType,
|
||||
}
|
||||
};
|
||||
|
||||
// here, for once, we're plainly returning NewType: we don't want cast to do weird things.
|
||||
// Returns NewType directly to avoid unintended intermediate conversions.
|
||||
|
||||
template <typename OldType, typename NewType>
|
||||
EIGEN_DEVICE_FUNC inline NewType cast(const OldType& x) {
|
||||
@@ -832,8 +832,8 @@ EIGEN_DEVICE_FUNC std::enable_if_t<(std::numeric_limits<T>::has_infinity && !Num
|
||||
|
||||
template <typename T>
|
||||
EIGEN_DEVICE_FUNC
|
||||
std::enable_if_t<!(std::numeric_limits<T>::has_quiet_NaN || std::numeric_limits<T>::has_signaling_NaN), bool>
|
||||
isnan_impl(const T&) {
|
||||
std::enable_if_t<!(std::numeric_limits<T>::has_quiet_NaN || std::numeric_limits<T>::has_signaling_NaN), bool>
|
||||
isnan_impl(const T&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1450,9 +1450,9 @@ EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
EIGEN_DEVICE_FUNC
|
||||
EIGEN_ALWAYS_INLINE std::enable_if_t<!(NumTraits<T>::IsSigned || NumTraits<T>::IsComplex), typename NumTraits<T>::Real>
|
||||
abs(const T& x) {
|
||||
EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE
|
||||
std::enable_if_t<!(NumTraits<T>::IsSigned || NumTraits<T>::IsComplex), typename NumTraits<T>::Real>
|
||||
abs(const T& x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ struct Assignment<DstXprType,
|
||||
|
||||
//----------------------------------------
|
||||
// Catch "Dense ?= xpr + Product<>" expression to save one temporary
|
||||
// FIXME we could probably enable these rules for any product, i.e., not only Dense and DefaultProduct
|
||||
// FIXME: consider enabling these rules for all product types, not only Dense and DefaultProduct.
|
||||
|
||||
template <typename OtherXpr, typename Lhs, typename Rhs>
|
||||
struct evaluator_assume_aliasing<
|
||||
@@ -1158,7 +1158,7 @@ struct generic_product_impl<Lhs, Inverse<Rhs>, MatrixShape, PermutationShape, Pr
|
||||
* Products with transpositions matrices
|
||||
***************************************************************************/
|
||||
|
||||
// FIXME could we unify Transpositions and Permutation into a single "shape"??
|
||||
// FIXME: consider unifying Transpositions and Permutation into a single shape.
|
||||
|
||||
/** \internal
|
||||
* \class transposition_matrix_product
|
||||
|
||||
@@ -43,7 +43,7 @@ struct traits<Ref<PlainObjectType_, Options_, StrideType_> >
|
||||
OuterStrideMatch = IsVectorAtCompileTime || int(OuterStrideAtCompileTime) == int(Dynamic) ||
|
||||
int(OuterStrideAtCompileTime) == int(Derived::OuterStrideAtCompileTime),
|
||||
// NOTE, this indirection of evaluator<Derived>::Alignment is needed
|
||||
// to workaround a very strange bug in MSVC related to the instantiation
|
||||
// to work around an MSVC bug related to the instantiation
|
||||
// of has_*ary_operator in evaluator<CwiseNullaryOp>.
|
||||
// This line is surprisingly very sensitive. For instance, simply adding parenthesis
|
||||
// as "DerivedAlignment = (int(evaluator<Derived>::Alignment))," will make MSVC fail...
|
||||
|
||||
@@ -40,8 +40,7 @@ inline void stable_norm_kernel(const ExpressionType& bl, Scalar& ssq, Scalar& sc
|
||||
scale = maxCoeff;
|
||||
}
|
||||
|
||||
// TODO if the maxCoeff is much much smaller than the current scale,
|
||||
// then we can neglect this sub vector
|
||||
// TODO: skip sub-vector when maxCoeff << current scale.
|
||||
if (scale > Scalar(0)) // if scale==0, then bl is 0
|
||||
ssq += (bl * invScale).squaredNorm();
|
||||
}
|
||||
|
||||
@@ -1407,12 +1407,12 @@ EIGEN_STRONG_INLINE Packet8l preverse(const Packet8l& a) {
|
||||
|
||||
template <>
|
||||
EIGEN_STRONG_INLINE Packet16f pabs(const Packet16f& a) {
|
||||
// _mm512_abs_ps intrinsic not found, so hack around it
|
||||
// _mm512_abs_ps intrinsic not found, so implement via bitwise AND with sign-bit mask.
|
||||
return _mm512_castsi512_ps(_mm512_and_si512(_mm512_castps_si512(a), _mm512_set1_epi32(0x7fffffff)));
|
||||
}
|
||||
template <>
|
||||
EIGEN_STRONG_INLINE Packet8d pabs(const Packet8d& a) {
|
||||
// _mm512_abs_ps intrinsic not found, so hack around it
|
||||
// _mm512_abs_pd intrinsic not found, so implement via bitwise AND with sign-bit mask.
|
||||
return _mm512_castsi512_pd(_mm512_and_si512(_mm512_castpd_si512(a), _mm512_set1_epi64(0x7fffffffffffffff)));
|
||||
}
|
||||
template <>
|
||||
|
||||
@@ -55,7 +55,7 @@ EIGEN_STRONG_INLINE int64_t predux(const Packet8l& a) {
|
||||
// MSVC's _mm512_reduce_mul_epi64 is borked, at least up to and including 1939.
|
||||
// alignas(64) int64_t data[] = { 1,1,-1,-1,1,-1,-1,-1 };
|
||||
// int64_t out = _mm512_reduce_mul_epi64(_mm512_load_epi64(data));
|
||||
// produces garbage: 4294967295. It seems to happen whenever the output is supposed to be negative.
|
||||
// produces garbage: 4294967295. This occurs when the result should be negative.
|
||||
// Fall back to a manual approach:
|
||||
template <>
|
||||
EIGEN_STRONG_INLINE int64_t predux_mul(const Packet8l& a) {
|
||||
|
||||
@@ -294,7 +294,7 @@ EIGEN_DEFINE_FUNCTION_ALLOWING_MULTIPLE_DEFINITIONS Packet plog_impl_double(cons
|
||||
Packet x2 = pmul(x, x);
|
||||
Packet x3 = pmul(x2, x);
|
||||
|
||||
// Evaluate the polynomial approximant , probably to improve instruction-level parallelism.
|
||||
// Evaluate the polynomial in factored form for better instruction-level parallelism.
|
||||
// y = x - 0.5*x^2 + x^3 * polevl( x, P, 5 ) / p1evl( x, Q, 5 ) );
|
||||
Packet y, y1, y_;
|
||||
y = pmadd(cst_cephes_log_p0, x, cst_cephes_log_p1);
|
||||
@@ -1861,8 +1861,8 @@ struct accurate_log2<double> {
|
||||
// This function implements the non-trivial case of pow(x,y) where x is
|
||||
// positive and y is (possibly) non-integer.
|
||||
// Formally, pow(x,y) = exp2(y * log2(x)), where exp2(x) is shorthand for 2^x.
|
||||
// TODO(rmlarsen): We should probably add this as a packet up 'ppow', to make it
|
||||
// easier to specialize or turn off for specific types and/or backends.x
|
||||
// TODO(rmlarsen): We should probably add this as a packet op 'ppow', to make it
|
||||
// easier to specialize or turn off for specific types and/or backends.
|
||||
template <typename Packet>
|
||||
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Packet generic_pow_impl(const Packet& x, const Packet& y) {
|
||||
typedef typename unpacket_traits<Packet>::type Scalar;
|
||||
|
||||
@@ -249,8 +249,8 @@ void evaluateProductBlockingSizesHeuristic(Index& k, Index& m, Index& n, Index n
|
||||
|
||||
// Here, nc is chosen such that a block of kc x nc of the rhs fit within half of L2.
|
||||
// The second half is implicitly reserved to access the result and lhs coefficients.
|
||||
// When k<max_kc, then nc can arbitrarily growth. In practice, it seems to be fruitful
|
||||
// to limit this growth: we bound nc to growth by a factor x1.5.
|
||||
// When k<max_kc, then nc can grow without bound. In practice, it seems to be fruitful
|
||||
// to limit this growth: we bound nc growth to a factor of 1.5x.
|
||||
// However, if the entire lhs block fit within L1, then we are not going to block on the rows at all,
|
||||
// and it becomes fruitful to keep the packed rhs blocks in L1 if there is enough remaining space.
|
||||
Index max_nc;
|
||||
@@ -587,8 +587,7 @@ class gebp_traits<std::complex<RealScalar>, RealScalar, ConjLhs_, false, Arch, P
|
||||
}
|
||||
|
||||
EIGEN_STRONG_INLINE void loadRhsQuad_impl(const RhsScalar* b, RhsPacket& dest, const true_type&) const {
|
||||
// FIXME we can do better!
|
||||
// what we want here is a ploadheight
|
||||
// FIXME: replace with a dedicated ploadheight operation for more efficient quad loading.
|
||||
RhsScalar tmp[4] = {b[0], b[0], b[1], b[1]};
|
||||
dest = ploadquad<RhsPacket>(tmp);
|
||||
}
|
||||
@@ -669,7 +668,7 @@ DoublePacket<typename unpacket_traits<Packet>::half> predux_half(
|
||||
const DoublePacket<Packet>& a,
|
||||
std::enable_if_t<unpacket_traits<Packet>::size >= 16 &&
|
||||
!NumTraits<typename unpacket_traits<Packet>::type>::IsComplex>* = 0) {
|
||||
// yes, that's pretty hackish :(
|
||||
// Workaround: reduce real packets to half size by reinterpreting as complex.
|
||||
DoublePacket<typename unpacket_traits<Packet>::half> res;
|
||||
typedef std::complex<typename unpacket_traits<Packet>::type> Cplx;
|
||||
typedef typename packet_traits<Cplx>::type CplxPacket;
|
||||
@@ -689,7 +688,7 @@ void loadQuadToDoublePacket(const Scalar* b, DoublePacket<RealPacket>& dest,
|
||||
template <typename Scalar, typename RealPacket>
|
||||
void loadQuadToDoublePacket(const Scalar* b, DoublePacket<RealPacket>& dest,
|
||||
std::enable_if_t<unpacket_traits<RealPacket>::size == 16>* = 0) {
|
||||
// yes, that's pretty hackish too :(
|
||||
// Workaround: load quad elements by reinterpreting real packets as complex.
|
||||
typedef typename NumTraits<Scalar>::Real RealScalar;
|
||||
RealScalar r[4] = {numext::real(b[0]), numext::real(b[0]), numext::real(b[1]), numext::real(b[1])};
|
||||
RealScalar i[4] = {numext::imag(b[0]), numext::imag(b[0]), numext::imag(b[1]), numext::imag(b[1])};
|
||||
|
||||
@@ -383,8 +383,8 @@ struct generic_product_impl<Lhs, Rhs, DenseShape, DenseShape, GemmProduct>
|
||||
// to determine the following heuristic.
|
||||
// EIGEN_GEMM_TO_COEFFBASED_THRESHOLD is typically defined to 20 in GeneralProduct.h,
|
||||
// unless it has been specialized by the user or for a given architecture.
|
||||
// Note that the condition rhs.rows()>0 was required because lazy product is (was?) not happy with empty inputs.
|
||||
// I'm not sure it is still required.
|
||||
// Note that the condition rhs.rows()>0 was required because lazy product did not handle empty inputs
|
||||
// correctly. It is unclear whether this guard is still necessary.
|
||||
if ((rhs.rows() + dst.rows() + dst.cols()) < EIGEN_GEMM_TO_COEFFBASED_THRESHOLD && rhs.rows() > 0)
|
||||
lazyproduct::eval_dynamic(dst, lhs, rhs, internal::assign_op<typename Dst::Scalar, Scalar>());
|
||||
else {
|
||||
|
||||
@@ -182,7 +182,7 @@ EIGEN_STRONG_INLINE void parallelize_gemm(const Functor& func, Index rows, Index
|
||||
|
||||
// compute the maximal number of threads from the total amount of work:
|
||||
double work = static_cast<double>(rows) * static_cast<double>(cols) * static_cast<double>(depth);
|
||||
double kMinTaskSize = 50000; // FIXME improve this heuristic.
|
||||
double kMinTaskSize = 50000; // FIXME: tune this minimum task-size heuristic based on architecture and scalar type.
|
||||
pb_max_threads = std::max<Index>(1, std::min<Index>(pb_max_threads, static_cast<Index>(work / kMinTaskSize)));
|
||||
|
||||
// compute the number of threads we are going to use
|
||||
|
||||
@@ -212,7 +212,7 @@ struct trmv_selector<Mode, ColMajor> {
|
||||
ResScalar actualAlpha = alpha * lhs_alpha * rhs_alpha;
|
||||
|
||||
// FIXME find a way to allow an inner stride on the result if packet_traits<Scalar>::size==1
|
||||
// on, the other hand it is good for the cache to pack the vector anyways...
|
||||
// On the other hand, it is good for the cache to pack the vector anyways...
|
||||
constexpr bool EvalToDestAtCompileTime = Dest::InnerStrideAtCompileTime == 1;
|
||||
constexpr bool ComplexByReal = (NumTraits<LhsScalar>::IsComplex) && (!NumTraits<RhsScalar>::IsComplex);
|
||||
constexpr bool MightCannotUseDest = (Dest::InnerStrideAtCompileTime != 1) || ComplexByReal;
|
||||
|
||||
@@ -357,7 +357,7 @@
|
||||
// notice that since these are C headers, the extern "C" is theoretically needed anyways.
|
||||
extern "C" {
|
||||
// In theory we should only include immintrin.h and not the other *mmintrin.h header files directly.
|
||||
// Doing so triggers some issues with ICC. However old gcc versions seems to not have this file, thus:
|
||||
// Doing so triggers some issues with ICC. However old gcc versions may not have this file, thus:
|
||||
#if EIGEN_COMP_ICC >= 1110 || EIGEN_COMP_EMSCRIPTEN
|
||||
#include <immintrin.h>
|
||||
#else
|
||||
@@ -388,7 +388,7 @@ extern "C" {
|
||||
#define EIGEN_VECTORIZE_VSX 1
|
||||
#define EIGEN_VECTORIZE_FMA
|
||||
#include <altivec.h>
|
||||
// We need to #undef all these ugly tokens defined in <altivec.h>
|
||||
// We need to #undef macros defined by <altivec.h> that conflict with standard C++ names.
|
||||
// => use __vector instead of vector
|
||||
#undef bool
|
||||
#undef vector
|
||||
@@ -400,7 +400,7 @@ extern "C" {
|
||||
#define EIGEN_VECTORIZE_ALTIVEC
|
||||
#define EIGEN_VECTORIZE_FMA
|
||||
#include <altivec.h>
|
||||
// We need to #undef all these ugly tokens defined in <altivec.h>
|
||||
// We need to #undef macros defined by <altivec.h> that conflict with standard C++ names.
|
||||
// => use __vector instead of vector
|
||||
#undef bool
|
||||
#undef vector
|
||||
|
||||
@@ -1139,7 +1139,7 @@ EIGEN_DEVICE_FUNC constexpr void ignore_unused_variable(const T&) {}
|
||||
|
||||
#if EIGEN_COMP_MSVC
|
||||
// NOTE MSVC often gives C4127 warnings with compiletime if statements. See bug 1362.
|
||||
// This workaround is ugly, but it does the job.
|
||||
// This workaround suppresses MSVC C4127 warnings for compile-time conditionals.
|
||||
#define EIGEN_CONST_CONDITIONAL(cond) (void)0, cond
|
||||
#else
|
||||
#define EIGEN_CONST_CONDITIONAL(cond) cond
|
||||
|
||||
@@ -435,9 +435,8 @@ struct greater_equal_zero_op {
|
||||
|
||||
/* reductions for lists */
|
||||
|
||||
// using auto -> return value spec makes ICC 13.0 and 13.1 crash here, so we have to hack it
|
||||
// together in front... (13.0 doesn't work with array_prod/array_reduce/... anyway, but 13.1
|
||||
// does...
|
||||
// Using auto -> return value spec makes ICC 13.0 and 13.1 crash here,
|
||||
// so the return type is specified explicitly using decltype.
|
||||
template <typename... Ts>
|
||||
EIGEN_DEVICE_FUNC constexpr decltype(reduce<product_op, Ts...>::run((*((Ts*)0))...)) arg_prod(Ts... ts) {
|
||||
return reduce<product_op, Ts...>::run(ts...);
|
||||
|
||||
Reference in New Issue
Block a user