C++ Logo

std-proposals

Advanced search

Re: [std-proposals] coalesce function

From: Roberto Romani <roberto.romani_at_[hidden]>
Date: Mon, 4 Jul 2022 21:36:32 +0200
I agree it looks like an algorithm, except that the input parameters are a variadic and not iterables. I was rather think to consider it part of the utility library.

Thank you
Roberto

On 4 Jul 2022, 15:54 +0200, William Linkmeyer <wlink10_at_[hidden]>, wrote:
> Interesting. I looked over the header here:
>
> https://github.com/roroberto/cpp_small_simple_stupid_stuff/blob/main/Coalesce/Coalesce/coalesce.h
>
> And find the work clear enough.
>
> This work looks and feels like it belongs in one of the `algorithm` operations. Maybe it could be a `not_null_element`.
>
> 2C,
> WL
>
> > On Jul 3, 2022, at 4:23 PM, Roberto R via Std-Proposals <std-proposals_at_[hidden]> wrote:
> >
> > 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 · 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
> > --
> > Std-Proposals mailing list
> > Std-Proposals_at_[hidden]
> > https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2022-07-04 19:36:38