Reputation: 13313
I am trying to understand std::reference_wrapper
.
The following code shows that the reference wrapper does not behave exactly like a reference.
#include <iostream>
#include <vector>
#include <functional>
int main()
{
std::vector<int> numbers = {1, 3, 0, -8, 5, 3, 1};
auto referenceWrapper = std::ref(numbers);
std::vector<int>& reference = numbers;
std::cout << reference[3] << std::endl;
std::cout << referenceWrapper.get()[3] << std::endl;
// I need to use get ^
// otherwise does not compile.
return 0;
}
If I understand it correctly, the implicit conversion does not apply to calling member functions. Is this an inherent limitation? Do I need to use the std::reference_wrapper::get
so often?
Another case is this:
#include <iostream>
#include <functional>
int main()
{
int a = 3;
int b = 4;
auto refa = std::ref(a);
auto refb = std::ref(b);
if (refa < refb)
std::cout << "success" << std::endl;
return 0;
}
This works fine, but when I add this above the main
definition:
template <typename T>
bool operator < (T left, T right)
{
return left.someMember();
}
The compiler tries to instantiate the template and forgets about implicit conversion and the built in operator.
Is this behavior inherent or am I misunderstanding something crucial about the std::reference_wrapper
?
Upvotes: 57
Views: 39432
Reputation: 1695
About the second part of the question, why refa < refb
works as long as there is no template operator<
defined, it’s how C++’s function overloading works. AFAICT, it has nothing to do with the fact that it’s an operator.
The first thing you need to know is that std::reference_wrapper<T>
does not implement comparison operators before C++26. (In C++26, they’re implemented in the obvious way.) This means you get yourself in the messy space of overload resolution and template parameter substitution.
As it seems that the included headers declare no operator<
function and no operator<
function template with a successful substitution for your parameters (the std::reference_wrapper<int>
objects).
Before you have your template<typename T> operator<(T, T)
declared, overload resolution finds that it can call one operator<
, namely operator<(int, int)
if it does the user-defined implicit conversion on std::reference_wrapper<int>
.
With your template<typename T> operator<(T, T)
present, your code fails to compile. This is because Substitution Failure Is Not An Error (SFINAE for short). Substitution deals with inferring function template parameters based on the arguments passed to the function. Notably, substitution failure only cares about whether the signature is well-formed. If the function signature is fine, but something in the function body (its definition) goes wrong, it is not a substitution failure. You could use so-called SFINAE-friendly constructs such as std::enable_if
to make your operator<
function template fail substitution for a T
that has no someMember()
. That’s quite a bit of work, though.
Clearly, your function template operator<
is being used. Why?
Because overload resolution first generates a list of all candidates and then chooses the best among them. While the built-in operator<(int, int)
still is a candidate (via user-defined implicit conversion), the function operator< <std::reference_wrapper<int>>(std::reference_wrapper<int>, std::reference_wrapper<int>)
synthesized from your function template is a new competitor. For argument’s sake, let’s assume there aren’t any other candidates. (For reference, there are, such as operator<(long, long)
.) Those candidates are then ranked by how well they match the arguments in terms of how well the arguments convert to the parameter types. Those rules are complicated, but as a shortcut, an exact match is best and requiring user-defined conversions push a candidate down the ranks. For that reason, the synthesized function from your function template is a (much) better match than the built-in operator<(int, int)
.
Now, there are a tie-breaker rules that make non-template functions higher in the ranking than instances of a function template and rank-order template instances by degree of specialization:
- or, if not that,
F1
is a non-template function whileF2
is a template specialization- or, if not that,
F1
andF2
are both template specializations andF1
is more specialized according to the partial ordering rules for template specializations
Those tie-breakers are applied after considering how well arguments implicitly convert to parameter types.
For answering what you could do about the problem, I’ll assume you can’t change the template<typename T> bool operator<(T,T)
, otherwise that is the easiest solution by far.
If you just want to write refa < refb
(without .get()
), you could define operator<(std::reference_wrapper<int>, std::reference_wrapper<int>)
. As long as you don’t do that in the std
namespace, that’s okay. A lot of people dislike that for good reasons, but it might just get the job done. You can even use template<typename T> bool operator<(std::reference_wrapper<T>, std::reference_wrapper<T>)
as that one is more specialized than the one that fails. If you want to use the std::reference_wrapper<int>
as a std::map
key, probably the best solution is to write a class reference_wrapper_less
:
template<typename T>
struct reference_wrapper_less
{
bool operator(std::reference_wrapper<T> l, std::reference_wrapper<T> r)
{
return l.get() < r.get();
}
};
Then, you use maps of the form std::map<std::reference_wrapper<int>, Value, reference_wrapper_less>
.
Upvotes: 0
Reputation: 1695
The call to get
seems to be necessary because std::reference_wrapper
does not conditionally implement operator[]
. It does so for operator()
(see here). Confronted with a std::reference_wrapper<std::vector<int>>
that uses operator[]
, for some reason, C++ does not attempt conversions like the implicit conversion of std::reference_wrapper
to a reference. I’m not entirely sure why this is the case. I checked Clang and GCC (latest versions: v16.0.0 and v13.0.0) and they both reject it for C++11 through C++20 as well as C++1b.
Upvotes: 2
Reputation: 20523
Class std::reference_wrapper<T>
implements an implicit converting operator to T&
:
operator T& () const noexcept;
and a more explicit getter:
T& get() const noexcept;
The implicit operator is called when a T
(or T&
) is required. For instance
void f(some_type x);
// ...
std::reference_wrapper<some_type> x;
some_type y = x; // the implicit operator is called
f(x); // the implicit operator is called and the result goes to f.
However, sometimes a T
is not necessarily expected and, in this case, you must use get
. This happens, mostly, in automatic type deduction contexts. For instance,
template <typename U>
g(U x);
// ...
std::reference_wrapper<some_type> x;
auto y = x; // the type of y is std::reference_wrapper<some_type>
g(x); // U = std::reference_wrapper<some_type>
To get some_type
instead of std::reference_wrapper<some_type>
above you should do
auto y = x.get(); // the type of y is some_type
g(x.get()); // U = some_type
Alternativelly the last line above could be replaced by g<some_type>(x);
.
However, for templatized operators (e.g. ostream::operator <<()
) I believe you can't explicit the type.
Upvotes: 62