Date: Sun, 3 Jul 2022 22:23:34 +0200
Dear All
This is an idea that I proposed some time ago, I have collected the
feedbacks and made some change. It is about the function coalesce(T&&
default_value, Args&&... to_test_v), it looks for a not null value in
to_test_v and If it does not find a not null value then coalesce returns
default_value. It is similar to a coalesce functions implemented in
different RDMS.
Some examples:
int i = 5;
int* pi = &i;
std::optional<int> o = 12;
std::unique_ptr<int> up = std::make_unique<int>(3);
std::shared_ptr<int> sp = std::make_shared<int>(4);
std::weak_ptr<int> wp = sp;
the 1st parameter is the default value, the others are different type of
pointers or std::optional
const int r1 = coalesce(i, pi, o, up, sp, wp);
std::cout << r1 << std::endl; // prints 5 because the 1st pointer not
null is pi
Now if I define a function to calculate the default value
int calc_default_value() noexcept
{
return 14;
}
The default value can be a pointer to a function that will be called only if
no null pointer has been found
int r2 = coalesce(calc_default_value, nullptr);
std::cout << r2 << std::endl; // prints 14 the calculated default value
int r2b = coalesce(calc_default_value, pi);
std::cout << r2b << std::endl; // prints 5, the value pointed by pi
Other examples with more complex types
std::vector<int> ints{ 0,1,2,3,4,5 };
std::unique_ptr<std::vector<int>> upvn;
const size_t r3 = coalesce(ints, upvn).size();
std::cout << r3 << std::endl; // prints 6, the size of ints, the default
value because upwn is null
const std::vector<int>& v = coalesce(ints, upvn);
std::cout << v.size() << std::endl; // prints 6, the size of ints, the
default value because upwn is null
std::unique_ptr<std::vector<int>> upv =
std::make_unique<std::vector<int>>();
const size_t r4 = coalesce(ints, upvn, upv).size();
std::cout << r4 << std::endl; // prints 0, the size of upv
Let's define a couple of other functions
static int st = 123;
int* calc_value() noexcept
{
return &st;
}
int* calc_value_null() noexcept
{
return nullptr;
}
Not only the default value can be a pointer to a function, also any other
parameter.
The functions will be called one after the other until an not null value is
found.
const int r5 = coalesce(10, calc_value_null, calc_value, pi);
std::cout << r5 << std::endl; // prints 123, the value returned by
calc_value
Also lambda can be passed
auto l_null = []() noexcept -> int*
{
return nullptr;
};
const int r6 = coalesce(10, l_null, calc_value);
std::cout << r6 << std::endl; // prints 123, l_null returns nullptr,
therefore calc_value is called, that returns 123
Also std::function can be passed, in this case it will test also that the
object of type std::function points to a function
using fu = std::function<int* ()>;
fu f = l_null;
const int r7 = coalesce(2, f);
std::cout << r7 << std::endl; // it prints 2, the default value because
l_null, the function pointed by f, returns nullptr
fu fn;
const int r7b = coalesce(3, fn);
std::cout << r7b << std::endl; // it prints 2, the default value because
fn points to no function
Some other example of how coalesce can be used.
Let's say that we have a data feed that we want to process, and for each
record we must use the 1st value not null, if there is
std::vector<record> s1
{
{10, {}},
{{}, 20},
{{}, {}}
};
int r8 = std::accumulate(s1.begin(), s1.end(), 0,
[](int i, const record& r) { return i + coalesce(0, r.v1, r.v2);
});
std::cout << r8 << std::endl; // prints 30 = 10 + 20 + 0
for (int i : s1 | std::views::transform([](const record& r) { return
coalesce(0, r.v1, r.v2); }))
{
std::cout << i << " "; // prints first 10, then 20 and at the end 0
}
std::cout << std::endl;
Another data feed, the 1st field of the record is in grams and the second in
Kg
std::vector<record> s2
{
{1000, {}},
{{}, 2},
{{}, {}}
};
To process this feed I need to define another class.
Coalesce requires that the input parameters have defined the Boolean
operator (to check that the pointer is not null) and the indirection
operators.
template <typename Value, typename Proj>
class test_project
{
public:
test_project(Value v, Proj proj) : _v{ v }, _proj{ proj } {}
bool operator! () const noexcept { return !_v; }
auto operator *() { return std::invoke(_proj, *_v); }
private:
Value _v;
Proj _proj;
};
int r9 = std::accumulate(s2.begin(), s2.end(), 0,
[](int i, const record& r)
{ return i +
coalesce(0, r.v1,
test_project(r.v2,
[](int i) {return i * 1000; })); // with the help of
test_project and a lambda
//
we convert the second field in grams
}
);
std::cout << r9 << std::endl; // prints 3000 = 1000 + 2 * 1000
I made an implementation of coalesce it can be found here:
cpp_small_simple_stupid_stuff/Coalesce/Coalesce at main
<https://github.com/roroberto/cpp_small_simple_stupid_stuff/tree/main/Coales
ce/Coalesce> . roroberto/cpp_small_simple_stupid_stuff . GitHub
It is based on a set of concepts and 3 functions (2 are template
specialization of the 1st one).
/**
* Concept that define a pointer of type PointerType that points to
* ValueType. ValueType can be a function (an invocable).
* It covers: raw pointers, std::unique_ptr, std:shared_ptr,
* std::weak_ptr, std::optional, std::null_ptr, and any other
* class that has the operators * and bool.
*/
template<typename PointerType, typename ValueType>
concept pointer_to =
( std::invocable<ValueType>
&&
(
requires(PointerType pointer, ValueType value) // Pointer must
behave like a raw pointer to an object of class decltype(value())
{
{!pointer};
{*pointer} -> std::convertible_to<decltype(value())>;
}
|| requires(PointerType pointer, ValueType value) // Pointer
must behave like a weak_ptr that points to an object of class
decltype(value())
{
{*pointer.lock()} -> std::convertible_to<decltype(value())>;
}
)
)
||
( !std::invocable<ValueType>
&&
(
requires(PointerType pointer, ValueType) // Pointer must behave
like a raw pointer that points to a ValueType
{
{!pointer};
{*pointer} -> std::convertible_to<ValueType>;
}
|| requires(PointerType pointer, ValueType) // Pointer must
behave like a weak_ptr that points to a ValueType
{
{*pointer.lock()} -> std::convertible_to<ValueType>;
}
)
)
|| std::same_as<std::nullptr_t, PointerType>; // to cover the case of
null_ptr
/**
* Concept that define the requirements of the coalesce parameters, they
can
* be pointers or functions that return pointers.
*/
template<typename PointerType, typename ValueType>
concept coalesce_param =
requires(PointerType callable, ValueType)
{
{callable()} -> pointer_to<ValueType>;
}
|| pointer_to<PointerType, ValueType>;
/**
* It looks for a not null value in to_test_v. If it does
* not find it, then coalesce returns default_value. It is similar to
* a SQL coalesce function.
*
* \param default_value Value to return if to_test_0 and all to_test_v
are null
* \param ...to_test_v Next values to check.
* \return It looks for the first element of to_test_v not null.
* If all the values are null then coalesce returns
default_value.
*/
template<typename ReturnType, typename DefaultType,
coalesce_param<ReturnType>... Args>
requires std::convertible_to< DefaultType,
std::remove_reference_t<ReturnType> >
constexpr decltype(auto) coalesce(DefaultType&& default_value, Args&&...
to_test_v);
/**
* Specialized version of coalesce: the return type is a reference of
* DefaultType and DefaultType is not invocable.
*/
template<typename DefaultType, coalesce_param<DefaultType>... Args>
requires (!std::invocable<DefaultType>)
constexpr decltype(auto) coalesce(DefaultType&& default_value, Args&&...
to_test_v);
/**
* Specialized version of coalesce: DefaultType is invocable, the return
type of
* coalesce is the same return type of the function passed as default
parameter.
*/
template<typename DefaultType, coalesce_param<DefaultType>... Args>
requires std::invocable<DefaultType>
constexpr std::invoke_result_t<DefaultType> coalesce(DefaultType&&
default_value, Args&&... to_test_v);
What do you think about it?
Best Regards
Roberto
This is an idea that I proposed some time ago, I have collected the
feedbacks and made some change. It is about the function coalesce(T&&
default_value, Args&&... to_test_v), it looks for a not null value in
to_test_v and If it does not find a not null value then coalesce returns
default_value. It is similar to a coalesce functions implemented in
different RDMS.
Some examples:
int i = 5;
int* pi = &i;
std::optional<int> o = 12;
std::unique_ptr<int> up = std::make_unique<int>(3);
std::shared_ptr<int> sp = std::make_shared<int>(4);
std::weak_ptr<int> wp = sp;
the 1st parameter is the default value, the others are different type of
pointers or std::optional
const int r1 = coalesce(i, pi, o, up, sp, wp);
std::cout << r1 << std::endl; // prints 5 because the 1st pointer not
null is pi
Now if I define a function to calculate the default value
int calc_default_value() noexcept
{
return 14;
}
The default value can be a pointer to a function that will be called only if
no null pointer has been found
int r2 = coalesce(calc_default_value, nullptr);
std::cout << r2 << std::endl; // prints 14 the calculated default value
int r2b = coalesce(calc_default_value, pi);
std::cout << r2b << std::endl; // prints 5, the value pointed by pi
Other examples with more complex types
std::vector<int> ints{ 0,1,2,3,4,5 };
std::unique_ptr<std::vector<int>> upvn;
const size_t r3 = coalesce(ints, upvn).size();
std::cout << r3 << std::endl; // prints 6, the size of ints, the default
value because upwn is null
const std::vector<int>& v = coalesce(ints, upvn);
std::cout << v.size() << std::endl; // prints 6, the size of ints, the
default value because upwn is null
std::unique_ptr<std::vector<int>> upv =
std::make_unique<std::vector<int>>();
const size_t r4 = coalesce(ints, upvn, upv).size();
std::cout << r4 << std::endl; // prints 0, the size of upv
Let's define a couple of other functions
static int st = 123;
int* calc_value() noexcept
{
return &st;
}
int* calc_value_null() noexcept
{
return nullptr;
}
Not only the default value can be a pointer to a function, also any other
parameter.
The functions will be called one after the other until an not null value is
found.
const int r5 = coalesce(10, calc_value_null, calc_value, pi);
std::cout << r5 << std::endl; // prints 123, the value returned by
calc_value
Also lambda can be passed
auto l_null = []() noexcept -> int*
{
return nullptr;
};
const int r6 = coalesce(10, l_null, calc_value);
std::cout << r6 << std::endl; // prints 123, l_null returns nullptr,
therefore calc_value is called, that returns 123
Also std::function can be passed, in this case it will test also that the
object of type std::function points to a function
using fu = std::function<int* ()>;
fu f = l_null;
const int r7 = coalesce(2, f);
std::cout << r7 << std::endl; // it prints 2, the default value because
l_null, the function pointed by f, returns nullptr
fu fn;
const int r7b = coalesce(3, fn);
std::cout << r7b << std::endl; // it prints 2, the default value because
fn points to no function
Some other example of how coalesce can be used.
Let's say that we have a data feed that we want to process, and for each
record we must use the 1st value not null, if there is
std::vector<record> s1
{
{10, {}},
{{}, 20},
{{}, {}}
};
int r8 = std::accumulate(s1.begin(), s1.end(), 0,
[](int i, const record& r) { return i + coalesce(0, r.v1, r.v2);
});
std::cout << r8 << std::endl; // prints 30 = 10 + 20 + 0
for (int i : s1 | std::views::transform([](const record& r) { return
coalesce(0, r.v1, r.v2); }))
{
std::cout << i << " "; // prints first 10, then 20 and at the end 0
}
std::cout << std::endl;
Another data feed, the 1st field of the record is in grams and the second in
Kg
std::vector<record> s2
{
{1000, {}},
{{}, 2},
{{}, {}}
};
To process this feed I need to define another class.
Coalesce requires that the input parameters have defined the Boolean
operator (to check that the pointer is not null) and the indirection
operators.
template <typename Value, typename Proj>
class test_project
{
public:
test_project(Value v, Proj proj) : _v{ v }, _proj{ proj } {}
bool operator! () const noexcept { return !_v; }
auto operator *() { return std::invoke(_proj, *_v); }
private:
Value _v;
Proj _proj;
};
int r9 = std::accumulate(s2.begin(), s2.end(), 0,
[](int i, const record& r)
{ return i +
coalesce(0, r.v1,
test_project(r.v2,
[](int i) {return i * 1000; })); // with the help of
test_project and a lambda
//
we convert the second field in grams
}
);
std::cout << r9 << std::endl; // prints 3000 = 1000 + 2 * 1000
I made an implementation of coalesce it can be found here:
cpp_small_simple_stupid_stuff/Coalesce/Coalesce at main
<https://github.com/roroberto/cpp_small_simple_stupid_stuff/tree/main/Coales
ce/Coalesce> . roroberto/cpp_small_simple_stupid_stuff . GitHub
It is based on a set of concepts and 3 functions (2 are template
specialization of the 1st one).
/**
* Concept that define a pointer of type PointerType that points to
* ValueType. ValueType can be a function (an invocable).
* It covers: raw pointers, std::unique_ptr, std:shared_ptr,
* std::weak_ptr, std::optional, std::null_ptr, and any other
* class that has the operators * and bool.
*/
template<typename PointerType, typename ValueType>
concept pointer_to =
( std::invocable<ValueType>
&&
(
requires(PointerType pointer, ValueType value) // Pointer must
behave like a raw pointer to an object of class decltype(value())
{
{!pointer};
{*pointer} -> std::convertible_to<decltype(value())>;
}
|| requires(PointerType pointer, ValueType value) // Pointer
must behave like a weak_ptr that points to an object of class
decltype(value())
{
{*pointer.lock()} -> std::convertible_to<decltype(value())>;
}
)
)
||
( !std::invocable<ValueType>
&&
(
requires(PointerType pointer, ValueType) // Pointer must behave
like a raw pointer that points to a ValueType
{
{!pointer};
{*pointer} -> std::convertible_to<ValueType>;
}
|| requires(PointerType pointer, ValueType) // Pointer must
behave like a weak_ptr that points to a ValueType
{
{*pointer.lock()} -> std::convertible_to<ValueType>;
}
)
)
|| std::same_as<std::nullptr_t, PointerType>; // to cover the case of
null_ptr
/**
* Concept that define the requirements of the coalesce parameters, they
can
* be pointers or functions that return pointers.
*/
template<typename PointerType, typename ValueType>
concept coalesce_param =
requires(PointerType callable, ValueType)
{
{callable()} -> pointer_to<ValueType>;
}
|| pointer_to<PointerType, ValueType>;
/**
* It looks for a not null value in to_test_v. If it does
* not find it, then coalesce returns default_value. It is similar to
* a SQL coalesce function.
*
* \param default_value Value to return if to_test_0 and all to_test_v
are null
* \param ...to_test_v Next values to check.
* \return It looks for the first element of to_test_v not null.
* If all the values are null then coalesce returns
default_value.
*/
template<typename ReturnType, typename DefaultType,
coalesce_param<ReturnType>... Args>
requires std::convertible_to< DefaultType,
std::remove_reference_t<ReturnType> >
constexpr decltype(auto) coalesce(DefaultType&& default_value, Args&&...
to_test_v);
/**
* Specialized version of coalesce: the return type is a reference of
* DefaultType and DefaultType is not invocable.
*/
template<typename DefaultType, coalesce_param<DefaultType>... Args>
requires (!std::invocable<DefaultType>)
constexpr decltype(auto) coalesce(DefaultType&& default_value, Args&&...
to_test_v);
/**
* Specialized version of coalesce: DefaultType is invocable, the return
type of
* coalesce is the same return type of the function passed as default
parameter.
*/
template<typename DefaultType, coalesce_param<DefaultType>... Args>
requires std::invocable<DefaultType>
constexpr std::invoke_result_t<DefaultType> coalesce(DefaultType&&
default_value, Args&&... to_test_v);
What do you think about it?
Best Regards
Roberto
Received on 2022-07-03 20:23:37