Member function syntax should match function pointers, lambdas, and functors

61 views
Skip to first unread message

Antonio Perez

unread,
May 15, 2018, 1:09:13 PM5/15/18
to ISO C++ Standard - Future Proposals
Proposal: Allow member functions to be used as though "wrapped" in a lambda. 

To get a clearer picture of what this means, let's define a macro that wraps the input in a lambda:
#define LAMBDA(x) [&](auto&&... inputs) { return x(inputs...); }
(I an not proposing adding this macro to the standard, but rather using it to define the behavior of my proposed syntax)

If someone writes:
std::vector<int> v;
auto func = v.push_back; //Proposed syntax
This should be equivalent to:
std::vector<int> v;
//This will compile if LAMBDA is defined as above
auto func = LAMBDA(v.push_back);

It should also be possible to pass member functions to higher order functions:

template<class Func,class... Args>
void RepeatNTimes(size_t n, Func&& function,Args&&... arguments) {
   
for(size_t i=0;i<n;++i) {
      function(arguments...);
   
}
}
//...
//Later in the code:

std::vector<int> v {};
RepeatNTimes(5, v.push_back, 0); //Proposed syntax
//v now equals {0, 0, 0, 0, 0}

That last line would equivalent to:
RepeatNTimes(5, LAMBDA(v.push_back), 0);//This will compile if LAMBDA is defined as above

Sales Pitch: C++ is powerful precisely because it allows abstraction with minimal overhead. Thanks to highly aggressive compiler optimization, it's easy to write short, understandable, and expressive code that's also fast.

Member function are currently cumbersome to use with higher order functions, and their current syntax is complicated. Making it easy to pass member functions would add to the flexibility and power of C++, and would also make the syntax more uniform. Function pointers, lambdas, and functors can already be passed and assigned with the proposed syntax; why not member functions too?

Gašper Ažman

unread,
May 15, 2018, 1:14:44 PM5/15/18
to std-pr...@isocpp.org
Just a question - are you aware of std::invoke?



G

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/78d38a05-9633-43af-a04f-7a3895b72d53%40isocpp.org.

Antonio Perez

unread,
May 15, 2018, 2:37:29 PM5/15/18
to ISO C++ Standard - Future Proposals
I am aware of std::invoke.

The goal of my proposed addition is to make it easier to use member functions as callable objects. Currently you have to pass a pointer to the member function, as well as an object to call it on (meaning that higher order functions need additional overloads just to work with member functions). Why not allow passing the object and it's member function together as single callable object?
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.

Gašper Ažman

unread,
May 15, 2018, 2:48:24 PM5/15/18
to std-pr...@isocpp.org
Noted, thank you.

This is a very interesting proposal, but I think it's a bit much to bite off all at the same time, especially since it basically really does seem like it's standardizing a macro.

Imagine, like in your example, you want to bind vector.push_back. vector::push_back is a template - it does not have a first-class member at all, which means you are effectively taking tokens, generating a lambda using tokens (and not a pointer) and then calling it by name (and not some other way). It does strictly more than std::bind would, for instance.

I'd also like to see some investigation about what this syntax could conflict with, and which doors it closes in the future.

I'd like to see some interaction with the current "better-macros" proposals (lazy parameters, for instance) to see if this is implementable with them, or, if not, if it could be.

Other than that, looks good at first glance! That's about as far as I'm willing to go without the above, though :). We know this automatic binding works well in python, but the differences are large (python does not have overload sets).

G

To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Antonio Perez

unread,
May 15, 2018, 3:32:33 PM5/15/18
to ISO C++ Standard - Future Proposals
Thank you for your feedback! I'd like to make one change to the original post, and it's that arguments should be forwarded to ensure correct / expected behavior (the way the lambda macro was written in the original post, arguments weren't forwarded). 

This code illustrates the difference between when arguments are and aren't forwarded
//In Demo.cc:
#include <iostream>
#include <vector>
#include <functional>
#include <utility>

struct movable {
   int id;
   bool moved = false;
   movable() {
       static int num_created = 0;
       id = num_created++;
       std::cout << "[movable() called, id=" << id << "]" << std::endl;
   }
   movable(const movable& m) : id(m.id) {
       std::cout << "[movable(const&) called, id=" << id << "]" << std::endl;
   }
   movable(movable&& m) : id(m.id) {
       m.moved = true;
       std::cout << "[movable(&&) called, id=" << id << "]" << std::endl;
   }
   ~movable() {
       if(!moved) {
           std::cout << "[~movable() called, id=" << id << "]" << std::endl;
       }
   }
};

void move_and_discard(movable& m) {
   std::cout << "[recieved reference; didn't move]" << std::endl;
}
void move_and_discard(movable&& m) {
   movable dest = std::move(m);
   std::cout << "[moved object]" << std::endl;
}
int main() {
   {
       std::cout << "Direct call: " << std::endl;
       movable A {};
       move_and_discard(std::move(A));
   }
   {
       auto move_and_discard_lambda = [](auto&&... inputs) {
           move_and_discard(inputs...);
       };
       std::cout << "With lambda (unforwarded): " << std::endl;
       movable B {};
       move_and_discard_lambda(std::move(B));
   }
   {
       auto move_and_discard_lambda = [](auto&&... inputs) {
           move_and_discard(std::forward<decltype(inputs)>(inputs)...);
       };
       std::cout << "With lambda (forwarded): " << std::endl;
       movable C {};
       move_and_discard_lambda(std::move(C));
   }
}/* Terminal output:
$ c++-7 -std=c++17 -O3 -pedantic -Wall Demo.cc -o Demo && ./Demo
Direct call:
[movable() called, id=0]
[movable(&&) called, id=0]
[moved object]
[~movable() called, id=0]
With lambda (unforwarded):
[movable() called, id=1]
[recieved reference; didn't move]
[~movable() called, id=1]
With lambda (forwarded):
[movable() called, id=2]
[movable(&&) called, id=2]
[moved object]
[~movable() called, id=2]
*/

 

Richard Smith

unread,
May 15, 2018, 3:56:45 PM5/15/18
to std-pr...@isocpp.org

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Reply all
Reply to author
Forward
0 new messages