Cppcoreguidelines: guideline for preferring free functions over member functions

Created on 7 Mar 2018  ·  10Comments  ·  Source: isocpp/CppCoreGuidelines

CppCon 2017: Klaus Iglberger “Free Your Functions!” gives good reason why one should prefer free functions over member functions. Like for example std::begin(x) is better than x.begin(), std::size(x) is better than x.size(). Also functions like swap should be implemented as free functions and not as member functions (see Specializations under http://en.cppreference.com/w/cpp/algorithm/swap).

Looking at the samples in the guidelines I see that there is no consistent style. Sometimes the free functions are used, sometimes the member functions are used. In my opinion it would be a nice enhancement to have a consistent style and to have this backed by a guideline.

If there is interest I could prepare a PR at least for making the samples consistent.

Most helpful comment

I like the brevity of vec.size() over std::size(vec) and auto-complete makes the .size() easier to type (yes, readability should dominate writeability but here both win).

All 10 comments

Which specific cases are you thinking of changing?

I've looked through the slides and I don't find it entirely persuasive. It seems to be arguing that operations should be _implemented_ as free functions, which is fine, but doesn't really apply in the cases you mention which are about _using_ the functions ...

std::begin(x) is better than x.begin(), std::size(x) is better than x.size()

Those free functions don't work unless the members exist: the generic std::begin depends on a member begin existing, because the generic free function doesn't know the internals of every container and couldn't access them anyway. So given that the member is required to exist anyway, if you know you have a std::vector (not just a generic template parameter of some sequence type) then there is no benefit to calling std::begin(vec) instead of vec.begin().

Also functions like swap should be implemented as free functions and not as member functions

Or both. You need a free function for swap(a, b) to work, but it's typically implemented by calling a member function. If you know you're working with a concrete type T that has a T::swap(T&) member then it seems harmless to use it, rather than the more verbose idiomatic way to use non-member swap (the "std swap two step", also C.165: Use using for customization points):

using std::swap;
swap(a, b);

Those free functions don't work unless the members exist: the generic std::begin depends on a member begin existing

The generic one yes, but, say, a library can override it for their own custom type (which could be a primitive struct from a C library for example). The thing about free functions is that they are very useful especially in templates without incurring a template code on their part as well while being easily overrideable.

_/me really wishes the 'unified call syntax' was added to C++..._

@jwakely

std::begin(), std::end(), std::size() are a uniform syntax that work for containers with member functions begin/end/size and for C style arrays. They could also be specialized for user defined types that implement similar functions with other names (like for example Begin/End/Size). It is syntax that can be used in every case.

There is no performance or memory gain, it is only about simplicity and consistence: one same style everywhere.

@beinhaerter yes I know what they are and how they work. And my point is that when you have a concrete type like std::vector<int> the benefit of using the non-member form is debatable, and I'm unconvinced it deserves to be promoted by the guidelines. (As a data point, adding the non-member std::data and std::size functions broke working code, due to new naming conflicts, so it's arguable whether they should ever have been added to the standard at all).

I repeat the question, which specific examples are you proposing to change? I looked through the guidelines and none of the cases of v.begin() look worth changing. They're either in "bad" examples specifically showing poor style, or are in cases where the type is concrete and "it also works with arrays" is irrelevant. I didn't see any uses of member swap that could be improved. Maybe you should just create a PR to show what you're proposing.

@OvermindDL1 yes, again, I understand what they are for. I understand they are useful in templates. Please read what I've written again. When you are not in template, and you know the type you are using is a std::vector<int>, I am not convinced that using std::begin is better than the member. All the cases I saw in the guidelines either already use the non-member function, or deal with concrete (i.e. not in a template) types. So again, what specific changes are being proposed?

At the very least 'Consistency'. Calling the same type of 'actions' the same way in all cases regardless of type is a Good Thing.

This is also why I wish the Unified Function Call Syntax was accepted in C++17...

@jwakely My suggestion is to add a guideline to prefer using (and probably implementing) free functions over member functions. If this guideline is added (and only then) I suggest replacing all x.begin() (and end and size (and probably others)) in the samples by their corresponding free functions.

@johelegp Thanks for pointing that out, I did not see that guideline. That is at least the guideline for the implementing side. I think it would be good to also have a guideline for the using side.

I like the brevity of vec.size() over std::size(vec) and auto-complete makes the .size() easier to type (yes, readability should dominate writeability but here both win).

Thanks for the input. We think this is covered in C.4, as well as the references to "customization point" in the guidelines.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Epholys picture Epholys  ·  4Comments

franzhollerer picture franzhollerer  ·  6Comments

franzhollerer picture franzhollerer  ·  5Comments

tamaskenez picture tamaskenez  ·  6Comments

HowardHinnant picture HowardHinnant  ·  3Comments