Should All/Most Setter Functions in C++11 Be Written as Function Templates Accepting Universal References

Should all/most setter functions in C++11 be written as function templates accepting universal references?

You know the classes A and B, so you know if they are movable or not and if this design is ultimately necessary. For something like std::string, it's a waste of time changing the existing code unless you know you have a performance problem here. If you're dealing with auto_ptr, then it's time to rip it out and use unique_ptr.

It's usually preferred now to take arguments by value if you don't know anything more specific- such as

void set_a(A a) { _a = std::move(a); }

This permits the use of any of the constructors of A without requiring anything except movability and offers a relatively intuitive interface.

Should all C++ functions be declared taking rvalue from now?

Various stackexchange questions have helped me answer my own question.

I had forward and move confused. This answer helped (Can I typically/always use std::forward instead of std::move?) You can r/forward/move/g and my question is still the same. Be sure to read Scott Meyer's response to this question directly: http://scottmeyers.blogspot.co.uk/2012/11/on-superfluousness-of-stdmove.html

So is it a good idea? Well, these posts helped me figure that out: Is pass-by-value a reasonable default in C++11?

And

Should all/most setter functions in C++11 be written as function templates accepting universal references?

And

Should I always move on `sink` constructor or setter arguments?

C++ - Functions calling setters - how to decorate the arguments?

When you have a value you are not going to reuse, std::move it.

When you have an rvalue reference you are not going to reuse, std::move it.

When a forwarding reference, std::forward it if and only if you'd move it if it was an rvalue reference.

The rule to take by value presumes:

  1. You don't mind silently making a copy at the call site

  2. Moving is so cheap you treat it as free

  3. Copying is much more expensive than moving

If (1) is false, instead take an rvalue reference (A&&) in your sink parameter. Now callers have to explicitly make their copy.

If (2) is false, perfect forwarding, multiple overloads, type-erased emplacing, or a myriad of other more complex solutions should be used.

If (3) is false, don't bother with moveing. Just take by value.

If all 3 are true, then your set_ab looks like:

void set_ab(A a, B b) {
set_a(std::move(a));
set_b(std::move(b));
}

Is overloading on universal references now much safer with concepts in c++ 20

I would say no. I mean, concepts help, because the syntax is nicer than what we had before, but it's still the same problem.

Here's a real-life example: std::any is constructible from any type that is copy constructible. So there you might start with:

struct any {
template <class T>
requires std::copy_constructible<std::decay_t<T>>
any(T&&);

any(any const&);
};

The problem is, when you do something like this:

any a = 42; // calls any(T&&), with T=int
any b = a; // calls any(T&&), with T=any

Because any itself is, of course, copy constructible. This makes the constructor template viable, and a better match since it's a less-const-qualified reference.

So in order to avoid that (because we want b to hold an int, and not hold an any that holds an int) we have to remove ourselves from consideration:

struct any {
template <class T>
requires std::copy_constructible<std::decay_t<T>>
&& (!std::same_as<std::decay_t<T>, any>)
any(T&&);

any(any const&);
};

This is the same thing we had to do in C++17 and earlier when Scott Meyers wrote his book. At least, it's the same mechanism for resolving the problem - even if the syntax is better.

Template function for applying args to function

template <typename Func, typename ...Args>
decltype(auto) apply_args(Func &&f, Args &&...args) {
return f(std::forward<Args>(args)...);
}

Best way to create a setter function in C++

The two setter versions setA(A&& a) and setA(const A& a) can be combined into a single one using a forwarding reference (a.k.a. perfect forwarding):

template<typename A>
void setA(A&& a)
{
m_a = std::forward<A>(a);
}

The compiler will then synthesize either the rvalue- or lvalue-reference version as needed depending on the value category.

This also solves the issue of multi-value setters, as the right one will be synthesized depending on the value category of each parameter.


Having said that, keep in mind that setters are just regular functions; the object is technically already constructed by the time any setter can be called. In case of setA, if A has a non-trivial constructor, then an instance m_a would already have been (default-)constructed and setA would actually have to overwrite it.

That's why in modern C++, the focus is often not so much on move- vs. copy-, but on in-place construction vs. move/copy.

For example:

struct A {
A(int x) : m_x(x) {}

int m_x;
};

struct B {
template<typename T>
B(T&& a) : m_a(std::forward<T>(a)) {}

A m_a;
};

int main() {
B b{ 1 }; // zero copies/moves
}

The standard library also often offers "emplace"-style calls in addition to more traditional "push"/"add"-style calls. For example, vector::emplace takes the arguments needed to construct an element, and constructs one inside the vector, without having to copy or move anything.



Related Topics



Leave a reply



Submit