Please see code snippets (implementation of matrix multiplication) below.Is it possible to simplify them using nested pack expansion to have something like {{((a[r][k] * b[k][c]) + ...)...}...}?
#include <array>
#include <utility>
template<typename T, size_t R, size_t C>
using Matrix = std::array<std::array<T, C>, R>;
template<typename A, typename B>
using mul_el_t = decltype(std::declval<A>()[0][0] * std::declval<B>()[0][0]);
Helper to compute single element.
template<size_t R1, size_t C2, size_t... C1_R2, typename A, typename B>
auto _mat_mul_element(const A &a, const B &b, std::index_sequence<C1_R2...>)
{
return ((a[R1][C1_R2] * b[C1_R2][C2]) + ...);
}
Helper to compute particular row.
template<size_t R1, size_t... C2, typename C1_R2, typename A, typename B>
auto _mat_mul_row(const A &a, const B &b, std::index_sequence<C2...>, C1_R2 c1_r2)
-> std::array<mul_el_t<A, B>, sizeof...(C2)>
{
return {_mat_mul_element<R1, C2>(a, b, c1_r2)...};
}
This computes whole matrix using parameters packs.
template<size_t... R1, typename C2, typename C1_R2, typename A, typename B>
auto _mat_mul(const A &a, const B &b, std::index_sequence<R1...>, C2 c2, C1_R2 c1_r2)
-> Matrix<mul_el_t<A, B>, sizeof...(R1), C2::size()>
{
return {_mat_mul_row<R1>(a, b, c2, c1_r2)...};
}
And actual interface.
template<typename T, size_t R1, size_t C1_R2, size_t C2>
Matrix<T, R1, C2> operator*(const Matrix<T, R1, C1_R2> &a, const Matrix<T, C1_R2, C2> &b)
{
return _mat_mul(
a, b,
std::make_index_sequence<R1>{},
std::make_index_sequence<C2>{},
std::make_index_sequence<C1_R2>{}
);
};
UPDATE (looks like I was not clear about the actual problem I have)
When I am trying to replace _mat_mul with:
template<size_t... R1, size_t... C2, size_t... C1_R2, typename A, typename B>
auto _mat_mul(const A &a, const B &b,
std::index_sequence<R1...>,
std::index_sequence<C2...>,
std::index_sequence<C1_R2...>)
-> Matrix<mul_el_t<A, B>, sizeof...(R1), sizeof...(C2)>
{
return {{((a[R1][C1_R2] * b[C1_R2][C2]) + ...)...}...};
}
using Apple LLVM version 9.1.0 (clang-902.0.39.1) compilation fails with:
[ 50%] Building CXX object CMakeFiles/main.cpp.o
main.cpp:38:51: error: pack expansion does not contain any unexpanded parameter packs
return {{((a[R1][C1_R2] * b[C1_R2][C2]) + ...)...}...};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
I think the failure is expected since compiler doesn't know which pack to expand (R1, C2 or C1_R2) in each expansion block.
How can I hint the compiler in this situation (note, I can use any compiler)?
According to documentation nested pack expansion can be seen as an iterative process which starts with innermost pack expansion [3 dots]. Each pack expansion expands all parameter packs within subexpression which is contained by that pack expansion.
Thus {{((a[R1][C1_R2] * b[C1_R2][C2]) + ...)...}...} after first step becomes{{(a[0][0] * b[0][0] + a[1][1] * b[1][1])...}...} (R1/C2/C1_R2 are index_sequence<2>). So next two pack expansions just have nothing to expand.
Naive solution
It's possible to move each parameter pack to required subexpression at the same time leaving actual value it carries in the desired place. One can use analogue of FP's let ... in expression:
auto let = [](auto a, auto f) { return f(a); };
Thus original expression becomes:
{let(R1, [&](auto r1) {
return std::array<T, sizeof...(C2)>{let(C2, [&](auto c2) {
return ((a[r1][C1_R2] * b[C1_R2][c2]) + ...);
})...};
})...};
That is probably good enough, but may take some time to decipher what's going on there. Also one would still need to introduce those parameter packs in the scope.
Better solution
One could try to improve readability by abstracting over parameter pack introduction/expansion. Using following utility function.
template<typename H, typename F, typename T, T... I>
decltype(auto) repack(std::integer_sequence<T, I...>, H h, F f)
{
return h(f(std::integral_constant<T, I>{})...);
}
This function takes value which carries some pack (one could make an overload for something other than std::integer_sequence), function f which is applied to each element of the pack, and function h which is used to convert final pack to some value.
Thus full multiply routine becomes
template<typename T, size_t R1, size_t C1_R2, size_t C2>
Matrix<T, R1, C2> operator*(const Matrix<T, R1, C1_R2> &a, const Matrix<T, C1_R2, C2> &b)
{
std::make_index_sequence<R1> r1{};
std::make_index_sequence<C2> c2{};
std::make_index_sequence<C1_R2> c1_r2{};
return repack(r1, ctor<Matrix<T, R1, C2>>(), [&](auto r1) {
return repack(c2, ctor<std::array<T, C2>>(), [&](auto c2) {
return repack(c1_r2, sum, [&](auto c1_r2) {
return a[r1][c1_r2] * b[c1_r2][c2];
});
});
});
}
where ctor is
template<typename H>
auto ctor()
{
return [](auto... xs) { return H{xs...}; };
}
and sum = [](auto... xs) { return (xs +...); };.
Crazy solution
One might have spot pattern in expression with 3 nested repack's, thus multiplication routine may become:
template<typename T, size_t R1, size_t C1_R2, size_t C2>
Matrix<T, R1, C2> operator*(const Matrix<T, R1, C1_R2> &a, const Matrix<T, C1_R2, C2> &b)
{
auto item = [&](auto r1, auto c2, auto c1_r2) { return a[r1][c1_r2] * b[c1_r2][c2]; };
auto curried_repack = curry(POLY(repack));
auto m = curried_repack(std::make_index_sequence<R1>{}, ctor<Matrix<T, R1, C2>>());
auto r = curried_repack(std::make_index_sequence<C2>{}, ctor<std::array<T, C2>>());
auto e = curried_repack(std::make_index_sequence<C1_R2>{}, sum);
auto op = [](auto w, auto f) {
return compose(w, curry(f));
};
return foldr(op, m, r, e, item)();
}
With utilities:
template<typename F>
auto curry(F f)
{
return [=](auto... a) {
return [=](auto... b) { return f(a..., b...); };
};
};
template<typename F, typename G>
auto compose(F f, G g)
{
return [=](auto... xs) {
return f(g(xs...));
};
};
And macro to convert template function into value
#define POLY(f) ([](auto... a){ return f(a...); })
And foldr which is left as a homework.
Compilation note
All solutions are equivalent in sense they produce the same binary.
Related
How can I execute a mathematical operation between two boost::multi_arrays?
Example of adding two arrays with value type double:
auto array1 = boost::multi_array<double, 2>(boost::extents[10][10]);
auto array2 = boost::multi_array<double, 2>(boost::extents[10][10]);
auto array3 = array1 + array2; //Does not compile
One possibility I know is the Intel IPP library. Adding two matrices can be done with e.g. ippiAdd_. But Intel IPP does unfortunately not support double values for adding.
So does someone know another library than Intel IPP respectively a solution to overcome the shortcomings of restricted value types in Intel IPP?
You could "just write it":
namespace ArrayOperators {
template <typename L, typename R>
static inline auto operator+(L const& l, R const& r) {
return ArrayOp {std::plus<>{}} (l, r); }
template <typename L, typename R>
static inline auto operator-(L const& l, R const& r) {
return ArrayOp {std::minus<>{}} (l, r); }
template <typename L, typename R>
static inline auto operator/(L const& l, R const& r) {
return ArrayOp {std::divides<>{}} (l, r); }
template <typename L, typename R>
static inline auto operator*(L const& l, R const& r) {
return ArrayOp {std::multiplies<>{}} (l, r); }
}
Of course, this requires us to actually implement the ArrayOp calleable. I took the liberty to
implement it for heterogeneous arrays (so when left and right hand have different element type)
implement it for the case where the right-hand side is not an array, in which case the scalar operand will be applied to every element of the left-hand-side
I didn't support
in-place operations
array ref/array (const) view
arrays of differing shapes or dimensionality
Here goes:
template <typename Op> struct ArrayOp {
Op op;
explicit ArrayOp(Op op) : op(op) {}
template <typename T, typename Scalar, size_t Dim> auto operator()(
boost::multi_array<T, Dim> const& l,
Scalar const& v) const
{
std::array<int, Dim> shape;
std::copy_n(l.shape(), Dim, shape.data());
using R = boost::multi_array<decltype(op(T{}, v)), Dim>;
R result(shape);
std::transform(
l.data(), l.data()+l.num_elements(),
result.data(),
[&op=op,v](auto const& el) { return op(el, v); });
return result;
}
template <typename T, typename U, size_t Dim> auto operator()(
boost::multi_array<T, Dim> const& l,
boost::multi_array<U, Dim> const& r) const
{
std::array<int, Dim> shape;
std::copy_n(l.shape(), Dim, shape.data());
assert(std::equal(shape.begin(), shape.end(), r.shape()));
using R = boost::multi_array<decltype(op(T{}, U{})), Dim>;
R result(shape);
std::transform(
l.data(), l.data()+l.num_elements(),
r.data(), result.data(),
[&op=op](auto const& v1, auto const& v2) { return op(v1, v2); });
return result;
}
};
Basically it comes down to
deduce resulting array element type and shape
do a unary or binary transform (depending on scalar/array rhs)
Now we can write the program:
Live On Compiler Explorer
int main() {
using MA = boost::multi_array<int, 2>;
auto shape = boost::extents[3][3];
MA array1(shape), array2(shape);
std::generate_n(array1.data(), array1.num_elements(),
[n = 0]() mutable { return n+=100; });
std::generate_n(array2.data(), array2.num_elements(),
[n = 0]() mutable { return n+=1; });
fmt::print("array1:\n\t{}\n", fmt::join(array1,"\n\t"));
fmt::print("array2:\n\t{}\n", fmt::join(array2,"\n\t"));
using namespace ArrayOperators;
auto array3 = (array1 + array2)/100.0;
fmt::print("array3:\n\t{}\n", fmt::join(array3,"\n\t"));
}
And it prints
array1:
{100, 200, 300}
{400, 500, 600}
{700, 800, 900}
array2:
{1, 2, 3}
{4, 5, 6}
{7, 8, 9}
array3:
{1.01, 2.02, 3.03}
{4.04, 5.05, 6.06}
{7.07, 8.08, 9.09}
BUT WAIT, WHAT ARE YOU SOLVING
If you want matrix (not "array") operations use Boost uBlas, Eigen, Armadillo
If you want utmost perf, using SIMD/AVX2/GPU instructions, you can use Boost Compute
You have to overload the + operator for those your types of objects boost::multi_array<double, 2> with your desired implementation.
EDIT I just tried real-quick, apparently it was not so hard, but maybe needs more testing and review ;)
Here you go:
boost::multi_array<double, 2> operator+(boost::multi_array<double, 2> a, boost::multi_array<double, 2> b) {
boost::multi_array<double, 2> result = a;
for (size_t i=0; i<a.size(); ++i) {
for (size_t j=0; j<a[i].size(); ++j) {
result[i][j] = a[i][j] + b[i][j];
}
}
return result;
}
I have implemented a simple fold function in C++ that accepts a lambda, and can fold multiple vectors at the same time at compile time. I am wondering if it could be simplified in some manner (I have provided both a recursive version and an iteratively recursive version - I am unsure which should have better performance): https://godbolt.org/z/39pW81
Performance optimizations are also welcome - in that regard is any of the two approaches faster?
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperR(Function&& func, const type_identity& id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I>0)
{
return func(foldHelperR<I-1>(std::forward<Function>(func), id, head, tail...), head[I], tail[I]...);
}
else
{
return func(id, head[0], tail[0]...);
}
}
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperI(Function&& func, const type_identity id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I<N-1)
{
return foldHelperI<I+1>(std::forward<Function>(func), func(id, head[I], tail[I]...), head, tail...);
}
else
{
return func(id, head[N-1], tail[N-1]...);
}
}
template<typename type_identity, typename type_head, int N_head, typename ...type_tail, int ...N_tail, typename Function = void (const type_identity&, const type_head&, const type_tail&...)>
constexpr auto fold(Function&& func, const type_identity& id, const tvecn<type_head, N_head>& head, const tvecn<type_tail, N_tail>&... tail)
{
static_assert(std::is_invocable_v<Function, const type_identity&, const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments (possibly wrong argument count).");
static_assert(all_equal_v<N_head, N_tail...>, "Vector sizes must match.");
//return foldHelperR<N_head-1>(std::forward<Function>(func), id, head, tail...);
return foldHelperI<0>(std::forward<Function>(func), id, head, tail...);
}
int main()
{
tvecn<int,3> a(1,2,3);
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
}
and can fold multiple vectors at the same time at compile time
Not exactly: if you want to operate compile-time
(1) you have to define constexpr the tvecn constructor and
(2) you have to define constexpr the foldhelper function and
(3) you have to declare constexpr a
// VVVVVVVVV
constexpr tvecn<int,3> a(1,2,3);
(4) you have to place the result of fold in a constexpr variable (or, more generally speaking, in a place where the value is required compile time, as the size field of a C-style array, or a template value parameter, or a static_assert() test)
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, a, a);
I am wondering if it could be simplified in some manner
Sure.
First of all: if you can, avoid to reinventing the weel: your tvecn is a simplified version of std::array.
Suggestion: use std::array (if you can obviously)
Second: you tagged C++17 so you can use folding
Suggestion: use it also for all_equal
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
More in general: when you have to define a custom type traits that has to provide a number, inherit (if possible) from std::integral_constant (or std::bool_constant, or std::true_type, or std::false_type: all std::integral_constant specializations). So you automatically inherit all std::integral_constant facilities.
Third: almost all C++ standard uses std::size_t, not int, for sizes.
Suggestion: when you have to do with sizes, use std::size_t, not int. This way you can avoid a lot of annoying troubles.
Fourth: from main() you should return only EXIT_SUCCESS (usually zero) or EXIT_FAILURE (usually 1)
Suggestion: avoid things as
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
Fifth: never underestimate the power of the comma operator.
Suggestion: avoid recursion at all and use template folding also for the helper function; by example
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
that you can call as follows from fold()
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
The following is a full compiling, and simplified, example
#include <array>
#include <utility>
#include <iostream>
#include <type_traits>
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
template <typename type_identity, typename type_head, std::size_t N_head,
typename ...type_tail, std::size_t ...N_tail,
typename Function = void (type_identity const &,
type_head const &,
type_tail const & ...)>
constexpr auto fold (Function && func, type_identity const & id,
std::array<type_head, N_head> const & head,
std::array<type_tail, N_tail> const & ... tail)
{
static_assert( std::is_invocable_v<Function, const type_identity&,
const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments"
" (possibly wrong argument count).");
static_assert( all_equal_v<N_head, N_tail...>,
"Vector sizes must match.");
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
}
int main()
{
constexpr std::array<int, 3u> b{2, 5, 7};
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, b, b);
std::cout << f << std::endl;
}
With Fold expression, it might be:
template <typename F, typename Init, std::size_t... Is, typename... Arrays>
constexpr auto fold_impl(F&& f, Init init, std::index_sequence<Is...>, Arrays&&... arrays)
{
auto l = [&](Init init, std::size_t i){ return f(init, arrays[i]...); };
return ((init = l(init, Is)), ...);
}
template <typename F, typename Init, typename Array, typename ... Arrays>
constexpr auto fold(F&& f, Init init, Array&& array, Arrays&&... arrays)
{
static_assert(((arrays.size() == array.size()) && ...));
return fold_impl(f, init, std::make_index_sequence<array.size()>{}, array, arrays...);
}
Demo
Is there a way to explicitly specify which ... refers to which pack expansion? In my code I have two pack expansions that I want to apply at different levels:
template<typename T, int N>
struct MyArr
{
T e[N];
constexpr T& operator[](int i) { return e[i]; }
constexpr const T& operator[](int i) const { return e[i]; }
MyArr() : e{} {}
template<typename ...type_pack>
MyArr(const type_pack&... pack) : e{pack...}
{
static_assert(sizeof...(pack)==N,
"Argument count must match the size.");
}
};
template<typename type_lhs, typename type_rhs>
auto add(const type_lhs& lhs, const type_rhs& rhs)
{
return lhs + rhs;
}
template<int ...I, typename type_head, typename ...type_pack, int N, typename Function>
auto apply(Function&& op,
const MyArr<type_head,N>& head, const MyArr<type_pack,N>&... pack)
{
return MyArr<type_head,N>((op(head[I],(pack[I])...))...);
// expand pack[I]- ^ , ^ - expand I...
};
int main()
{
MyArr<int,3> a(1,2,3);
return apply<0,1,2>(add<int,int>, a, a);
}
Essentially, I want to get:
(op(head[0], get<0>(pack)[0], ..., get<M-1>(pack)[0]),
...,
op(head[N-1], get<0>(pack)[N-1], ..., get<M-1>(pack)[N-1]))
Thanks to OznOg's advice I got it to work through creating a function in the middle:
template<int ...I, typename type_head, typename ...type_pack, int N, typename Function>
auto apply(Function&& op,
const MyArr<type_head,N>& head, const MyArr<type_pack,N>&... pack)
{
auto op2 = [&](int i) { return op(head[i], pack[i]...);};
return MyArr<type_head,N>(op2(I)...);
};
In this particular case, the only way I see is the use of an helper function (getVal(), in the following example)
template <int I, typename type_head, typename ...type_pack, int N,
typename Function>
auto getVal (Function&& op, MyArr<type_head,N> const & head,
MyArr<type_pack,N> const & ... pack)
{ return op(head[I], pack[I]...); }
template <int ... Is, typename type_head, typename ...type_pack, int N,
typename Function>
auto apply (Function && op, MyArr<type_head,N> const & head,
MyArr<type_pack,N> const &... pack)
{ return MyArr<type_head,N>{ getVal<Is>(op, head, pack...)... }; }
The problem is that you have
(pack[I])...
so there is no way (as far I know) to say that the expansion is to be applied to pack and not to I.
With the intermediate function
//.......................VVV expand pack
getVal<Is>(op, head, pack...)...
//...........................^^^ expand Is
you can use parentheses to separate the levels.
But you have to separate pack and Is.
In this blog post, I saw Eric Niebler explaining his take on concept checking. It looked like a nice balance between simplicity and practicality for me so I wanted to give it a try.
To test things myself, I came up with a quick concept of vector spaces. Here is the code:
#include <range/v3/utility/concepts.hpp>
#include <array>
#include <algorithm>
#include <string>
struct vector_space
{
// Valid expressions
template<typename Field, typename Vector>
auto requires(Field &&f, Vector &&v1, Vector &&v2) -> decltype(
ranges::concepts::valid_expr(
f * v1,
v1 + v2
));
};
template<typename Field, typename Vector>
constexpr bool VectorSpace()
{
return ranges::concepts::models<vector_space, Field, Vector>();
}
template<typename Field, typename Vector,
CONCEPT_REQUIRES_(VectorSpace<Field, Vector>())>
void linear_comb(Field f1, Vector v1, Field f2, Vector v2)
{
return (f1 * v1) + (f2 * v2);
}
template <typename T, std::size_t dim>
std::array<T, dim> operator+(std::array<T, dim> const& a1,
std::array<T, dim> const& a2) {
std::array<T, dim> res;
std::transform(a1.begin(), a1.end(), a2.begin(),
res.begin(),
[](T const& e1, T const& e2) {
return e1 + e2;
});
return res;
}
template <typename Field, typename T, std::size_t dim>
std::array<T, dim> operator*(std::array<T, dim> const& a,
Field f) {
std::array<T, dim> res;
std::transform(a.begin(), a.end(),
res.begin(),
[f](T const& e) {
return f * e;
});
return res;
}
template <typename Field, typename T, std::size_t dim>
std::array<T, dim> operator*(Field f, std::array<T, dim> const& a) {
return a * f;
}
int main() {
std::string s1 = "hello";
std::string s2 = "world";
std::array<float, 4> a1{1.1f, 2.2f, 3.3f, 4.4f};
std::array<float, 4> a2{4.4f, 3.3f, 2.2f, 1.1f};
std::array<float, 4> a3 = (3.14f * a1) + (2.71f * a2);
linear_comb(3.14f, a1, 2.71f, a2);
}
As you can see, I want to check that given v1, v2 of Vector and f of Field, expressions f * v1 and v1 + v2 to make sense. In short, I want scalar multiplication and vector addition. Although I can correctly compute a3 in main, function linear_comb tells me that concept VectorSpace<Field, Vector> is not satisfied. It correctly deduces Field as float and Vector as std::array<float, 4>. Why is it not seeing above as valid expressions then?
I want to have a default function as a "Predicate" template in case the user doesn't provide one. So far I've been doing something like:
template<typename T>
struct simple_compare{
bool operator()(T const& a, T const& b){
return a > b;
}
};
template<typename T, typename Predicate=simple_compare<T> >
bool compare(T a, T b, Predicate pred) {
return pred(a, b);
}
Can this be done using template metaprogramming in C++ instead of having a struct with an overloaded () operator?
There is no need for template metaprogramming here. You can simply use overloading like shown in the answer by Davide Spataro to provide a version that doesn't take a predicate and just calls the full version with a default predicate. Or you can just use a default argument for your predicate:
template <typename T, typename Predicate = simple_compare<T>>
bool compare(T a, T b, Predicate pred = {}) {
return pred(a, b);
}
If you just want a generic functor that invokes the > operator, then you could also just make the operator () a template instead of the functor type itself and let the exact types to compare be deduced from the call:
struct simple_compare {
template <typename A, typename B>
bool operator()(A const& a, B const& b) const {
return a > b;
}
};
template <typename T, typename Predicate = simple_compare>
bool compare(T a, T b, Predicate pred = {}) {
return pred(a, b);
}
Also, the standard library already provides standard functors for invoking all sorts of operators. So instead of rolling your own, you could just use std::greater<T> or std::greater<void> in your example. Furthermore, I assume there is no real need to require your arguments to be copyable and of the same type:
template <typename A, typename B, typename Predicate = std::greater<void>>
bool compare(A const& a, B const& b, Predicate pred = {}) {
return pred(a, b);
}
You do not need fancy template metaprogramming things.
Simply create two versions of the template function. The one without the custom predicate will simply execute the default one.
Something as the following should works:
auto default_pred = [](const auto a, const auto b) {return a > b;};
auto custom_pred = [](const auto a, const auto b) {return a < b;};
template<typename T, typename Fn >
bool compare2(T a, T b, Fn pred) {
return pred(a, b);
}
template<typename T >
bool compare2(T a, T b) {
return default_pred (a, b);
}
int main(){
cout<<compare2(2, 4)<<endl;
cout<<compare2(10.2d, 4.5d, custom_pred)<<endl;
return 0;
}