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:
- is there even a way to disallow a specific type using C++ concepts? (perhaps similar to what I did)
- is there a better way to tell the compiler not to use the forwarding constructor? (I don't want to have to make the variable I'm copying constant or explicitly define the copy/move constructors if I don't need to)
- If there is a way to do what I'm suggesting, would this be a universally applicable solution to the problem Scott Meyers expresses?
- Does applying a template constraint to a type automatically stop the type from being a universal reference?
std::enable_if
. aren't there still quite a few cases where concepts drastically reduce the overhead and mental effort required? or is this much complexity required in most cases? – Impostor