C++ Logo

std-proposals

Advanced search

Re: call (invoke) member function or static member function of a class in generic context.

From: Nikolay Mihaylov <nmmm_at_[hidden]>
Date: Tue, 7 Jul 2020 23:35:43 +0300
I know I emailed already for this, but I have another example of how
*invoke_class_member* dramatically :) reduces the code repetition.

Here is the example:

First function itself:

I had code like this.

template<class PROTOCOL, class DB_ADAPTER, class CONNECTION>
> class KeyValueWorkerProcessor{
> // ...
>
> WorkerStatus do_save(){
> const auto &p = protocol_.getParams();
> if (p.size() != 1)
> return err_BadRequest_();
> db_.save();
> protocol_.response_ok(buffer_);
> return WorkerStatus::WRITE;
> }
> WorkerStatus do_reload(){
> const auto &p = protocol_.getParams();
> if (p.size() != 1)
> return err_BadRequest_();
> db_.reload();
> protocol_.response_ok(buffer_);
> return WorkerStatus::WRITE;
> }
> // ...

     DB_ADAPTER db_;

};


DB_ADAPTER is an unknown type. We only know it has two methods -
*DB_ADAPTER::save()* and *DB_ADAPTER::reload()*.
We do not know if these are normal methods or static methods.
Because of that, we need to repeat the whole function, and only difference
is *db_.reload();* vs *db_.save();*

now we can do this:

template<class PROTOCOL, class DB_ADAPTER, class CONNECTION>
> class KeyValueWorkerProcessor{
> // ...
>
> template<typename F>
> WorkerStatus do_save_(F func){
> const auto &p = protocol_.getParams();
> if (p.size() != 1)
> return err_BadRequest_();
> // db_."func"();
> invoke_class_member(db_, func);
> protocol_.response_ok(buffer_);
> return WorkerStatus::WRITE;
> }
> WorkerStatus do_save(){
> return do_save_(&DB_ADAPTER::save);
> }
> WorkerStatus do_reload(){
> return do_save_(&DB_ADAPTER::reload);
> }
> // ...
> DB_ADAPTER db_;
> };


Well now it is a *beautiful code* :)
No repetition and easy to understand even from a novice C++ programmer - he
/ she will immediately understand what db_save() and db_reload() do.

Nikolay

On Mon, May 25, 2020 at 6:51 PM Nikolay Mihaylov <nmmm_at_[hidden]> wrote:

> My naming skills are awful. Naming things is one of two hardest things in
> programming :)
>
> *Another point -*
>
> I have proof of concept LinkList implementation I made several years ago.
> Iterators there there looks like:
>
> class LinkList{
> //...
> auto begin() const -> iterator{
> return head_;
> }
>
> constexpr *static* auto LinkList::end() -> iterator{
> return nullptr;
> }
> }
>
> Since end() does not require anything from the object (or class) it is
> marked static.
>
> Now suppose some fancy programmer decides to do pointer to member function
> - begin() or end().
> How is this possible with the standard library?
>
> - begin() have 1 parameter - e.g. instance of the class (because it
> accesses private members, e.g. head_ )
> - end() have 0 parameters.
>
> With the current standard library there is no way this can be done except
> "do it yourself".
>
>
>
> On Mon, May 25, 2020 at 5:42 PM Garrett May <garrett.ls.may_at_[hidden]>
> wrote:
>
>> That is fine - all of that makes sense.
>>
>> However, I for one do not believe that such a class_invoke function is
>> necessary to add to the standard library, in my opinion. It looks from my
>> point of view to be required only in a specific situation where:
>> - you need to call a member function pointer, because you require the
>> (potentially private/protected) information inside the member; and
>> - you need to call a function pointer, because you don't need any
>> information from a member
>>
>> From a stylistic perspective, class_invoke sounds like an odd name. It
>> makes sense when you are calling a function pointer that resides inside a
>> type, but:
>> - for a member function pointer, you aren't calling a function of a class
>> - rather, a function of an instance of a class
>> - for a function pointer that doesn't reside inside a type, there is no
>> class - so it is also odd for it to be called by class_invoke
>>
>> And I think that highlights my point; function pointers and member
>> function pointers are different types. Only in rare circumstances do I
>> believe that the standard library should handle both generically - such as
>> in std::invoke, which can handle other callable cases. This sounds more
>> like a specific use case.
>>
>> (N.B. Nikolay Mihaylov, duplicate reply due to lack of std-proposals in
>> list of emailees)
>>
>>
>> On Mon, 25 May 2020 at 14:54, Nikolay Mihaylov <nmmm_at_[hidden]> wrote:
>>
>>> You are correct, this was my first implementation. I think "constexpr
>>> if" is not very readable and I usually prefer tag dispatch.
>>>
>>> About the arguments - yes, those are different types, but suppose the
>>> class is passed from somewhere, here is my real code:
>>>
>>> template<class PROTOCOL, class DB_ADAPTER, class CONNECTION>
>>>> class KeyValueWorkerProcessor{
>>>> // ...
>>>> template<typename F>
>>>> WorkerStatus do_accumulate_(F func){
>>>> const auto &p = protocol_.getParams();
>>>> if (p.size() != 4)
>>>> return err_BadRequest_();
>>>> const auto &key = p[1];
>>>> uint16_t const count = from_string<uint16_t>(p[2]);
>>>> const auto &prefix = p[3];
>>>> protocol_.response_strings(buffer_, *class_invoke(db_, func,
>>>> key, count, prefix)* );
>>>> return WorkerStatus::WRITE;
>>>> }
>>>> auto do_getx(){
>>>> return do_accumulate_(&DB_ADAPTER::getx);
>>>> }
>>>> auto do_count(){
>>>> return do_accumulate_(&DB_ADAPTER::count);
>>>> }
>>>> auto do_sum(){
>>>> return do_accumulate_(&DB_ADAPTER::sum);
>>>> }
>>>> auto do_min(){
>>>> return do_accumulate_(&DB_ADAPTER::min);
>>>> }
>>>> auto do_max(){
>>>> return do_accumulate_(&DB_ADAPTER::max);
>>>> }
>>>> // ...
>>>> private:
>>>> PROTOCOL &protocol_;
>>>> DB_ADAPTER &db_;
>>>> CONNECTION &buffer_;
>>>> };
>>>>
>>>
>>> My first implementation had do_accumulate_() pasted 5 times - It was OK,
>>> but very long and code duplication.
>>>
>>> Second implementation was with lambdas. It was rather confusing and hard
>>> to read.
>>>
>>> Third implementation used direct code - "(db_.*func)(key, count,
>>> prefix);", however I needed to remove "static" from my Mock adapter.
>>> That was OK, but I thought - what if later I really need to call a
>>> static method. Then I will need to do it as "normal" method and hope the
>>> optimizer cleans up the code for me.
>>>
>>> Current implementation works and looks very nice and clear, isn't it?
>>>
>>> Nick
>>>
>>>
>>> On Mon, May 25, 2020 at 3:37 PM Garrett May via Std-Proposals <
>>> std-proposals_at_[hidden]> wrote:
>>>
>>>> Are these not different types? One is a member function pointer, which
>>>> requires two arguments - a Real* and an int - whereas the other is a
>>>> function pointer which requires one argument - an int;
>>>>
>>>> You can do the following:
>>>>
>>>> template<typename C, typename F>
>>>> auto user(C &c, F func){
>>>> if constexpr(std::is_member_function_pointer<F>::value){
>>>> return (c.*func)(5);
>>>> } else{
>>>> return func(5);
>>>> }
>>>> }
>>>> And subsequently call via:
>>>>
>>>> *user(real, &Real::inc);*
>>>>
>>>> *user(real, Mock::inc);*
>>>>
>>>> On Mon, 25 May 2020, 1:16 pm Nikolay Mihaylov via Std-Proposals, <
>>>> std-proposals_at_[hidden]> wrote:
>>>>
>>>>> Hello,
>>>>>
>>>>> suppose you have following clases:
>>>>>
>>>>> struct Mock{
>>>>>> static int inc(int){
>>>>>> return 0;
>>>>>> } static int dec(int){
>>>>>> return 0;
>>>>>> }
>>>>>> };struct Real{
>>>>>> Real(int v) : v(v){} int inc(int a) const{
>>>>>> return a + v;
>>>>>> } int dec(int a) const{
>>>>>> return a - v;
>>>>>> }private:
>>>>>> int v;
>>>>>> };template<typename C, typename F>
>>>>>> auto user(C &c, F func){
>>>>>> return (c.*func)(5);
>>>>>> }
>>>>>
>>>>> This will allow to call:
>>>>>
>>>>>
>>>>> *user(real, &Real::inc);*
>>>>> but will not allow to do
>>>>>
>>>>> *user(real, &Mock::inc);*
>>>>>
>>>>> However because of generic context, both calls must be possible.
>>>>>
>>>>> Here is simple implementation of the feature:
>>>>>
>>>>> #include <type_traits>
>>>>>>
>>>>>> namespace class_invoke_impl_{
>>>>>> template <class T, class F, class... Args>
>>>>>> constexpr auto class_invoke_(T &&cl, F func, std::true_type,
>>>>>> Args&&... args){
>>>>>> return
>>>>>> (std::forward<T>(cl).*func)(std::forward<Args>(args)...);
>>>>>> }
>>>>>
>>>>>
>>>>>
>>>>> template <class T, class F, class... Args>
>>>>>> constexpr auto class_invoke_(T const &, F func, std::false_type,
>>>>>> Args&&... args){
>>>>>> return func(std::forward<Args>(args)...);
>>>>>> }
>>>>>> }
>>>>>
>>>>>
>>>>>
>>>>> template <class T, class F, class... Args>
>>>>>> constexpr auto class_invoke(T &&cl, F func, Args&&... args){
>>>>>> using namespace class_invoke_impl_;
>>>>>> return class_invoke_(std::forward<T>(cl), func,
>>>>>> std::is_member_pointer<F>{}, std::forward<Args>(args)...);
>>>>>> }
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Std-Proposals mailing list
>>>>> Std-Proposals_at_[hidden]
>>>>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>>>>
>>>> --
>>>> Std-Proposals mailing list
>>>> Std-Proposals_at_[hidden]
>>>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>>>
>>>

Received on 2020-07-07 15:39:36