In the book "Effective Modern C++" by Scott Meyers the advice is given (item 26/27) to "Avoid overloading on universal references". His rationale for this is that in almost all calls to an overloaded function that includes a universal reference, the compiler resolves to the universal reference even though that is often not the function you intend for it to resolve. (so this code is bad I think?)
template <typename T>
void foo(T&& t) {
// some sort of perfect forwarding
}
void foo(string&& t) {
// some sort of move operation
}
The example above is highly contrived and could likely be replaced with 2 functions.
Another example that I think would be harder to resolve and is far less contrived is one he actually gives in Item 26.
class Foo {
// a decent amount of private data that would take a while to copy
public:
// perfect forwarding constructor, usually the compiler resolves to this...
template <typename T>
explicit Foo(T&& t) : /* forward the information for construction */ {}
// constructor with some sort of rValue
explicit Foo(int);
// both the below are created by the compiler he says
// move constructor
Foo(Foo&& foo);
// copy constructor (used whenever the value passed in is const)
Foo(const Foo& foo);
}
// somewhere else in the code
Foo f{5};
auto f_clone(f);
Scott explains that instead of calling a move constructor or copy constructor, the forwarding constructor gets called in auto f_clone(f)
because the compiler rules are to resolve to the forward constructor first.
In the book, he explains alternatives to this and a few other examples of overloading on a universal reference. Most of them seem like good solutions for C++11/14/17 but I was thinking there were simpler ways to solve these problems with C++20 concepts. the code would be identical to the code above except for some sort of constraint on the forwarding constructor:
template <typename T>
requires = !(typename Foo) // not sure what would need to be put here, this is just a guess
explicit Foo(T&& t) : /* forward the information for construction */ {}
I don't know if that would be the correct syntax, I'm super new to C++ concepts
To me, C++ concepts applied to the forwarding function seem like a general solution that could be applied in every case but I'm not sure
there are multiple parts to my question:
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.