Date: Mon, 25 May 2020 18:51:08 +0300
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
>>>
>>
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-05-25 10:54:50