Date: Mon, 25 May 2020 15:41:50 +0100
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
>>
>
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 09:45:12