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: Wed, 8 Jul 2020 00:19:29 +0300
Thanks!

but what you sent me deals with pointers.
I try to deal with static functions.

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 userFunc(C &c, F func){
> return (c.*func)(5);
> }

This will allow to call:



*Real obj;userFunc(obj, &Real::inc);*
but will not allow to do



*Mock obj;userFunc(obj, &Mock::inc);*



On Wed, Jul 8, 2020 at 12:09 AM Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
wrote:

> See
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0312r1.html "Making
> Pointers to Members Callable" (Barry Revzin, 2017)
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1695.html "A
> Proposal to Make Pointers to Members Callable" (Peter Dimov, 2004)
>
> –Arthur
>
>
>
> On Tue, Jul 7, 2020 at 4:36 PM Nikolay Mihaylov via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> 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
>>>>>>
>>>>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>

Received on 2020-07-07 16:23:22