Suppose I have some object of type T
, and I want to put it into a reference wrapper:
int a = 5, b = 7;
std::reference_wrapper<int> p(a), q(b); // or "auto p = std::ref(a)"
Now I can readily say if (p < q)
, because the reference wrapper has a conversion to its wrapped type. All is happy, and I can process a collection of reference wrappers just like they were the original objects.
(As the question linked below shows, this can be a useful way to produce an alternate view of an existing collection, which can be rearranged at will without incurring the cost of a full copy, as well as maintaining update integrity with the original collection.)
However, with some classes this doesn't work:
std::string s1 = "hello", s2 = "world";
std::reference_wrapper<std::string> t1(s1), t2(s2);
return t1 < t2; // ERROR
My workaround is to define a predicate as in this answer*; but my question is:
Why and when can operators be applied to reference wrappers and transparently use the operators of the wrapped types? Why does it fail for std::string
? What has it got to do with the fact that std::string
is a template instance?
*) Update: In the light of the answers, it seems that using std::less<T>()
is a general solution.
Edit: Moved my guesswork to the bottom, here comes the normative text why this won't work. TL;DR version:
§14.8.3 [temp.over] p1
§14.8.2.1 [temp.deduct.call] p4
§14.8.1 [temp.arg.explicit] p6
Since
std::basic_string
depends on deduced template parameters (CharT
,Traits
), no conversions are allowed.This is kind of a chicken and egg problem. To deduce the template argument, it needs an actual instance of
std::basic_string
. To convert to the wrapped type, a conversion target is needed. That target has to be an actual type, which a class template is not. The compiler would have to test all possible instantiations ofstd::basic_string
against the conversion operator or something like that, which is impossible.Suppose the following minimal testcase:
If we don't provide the overload for an instantiation on
int
, the deduction fails. If we provide that overload, it's something the compiler can test against with the one allowed user-defined conversion (foo<int> const&
being the conversion target). Since the conversion matches in this case, overload resolution succeeds and we got our function call.std::reference_wrapper
does not have anoperator<
, so the only way to doref_wrapper<ref_wrapper
is via theref_wrapper
member:As you know,
std::string
is:The relevant declaration for
string<string
is:For
string<string
this function declaration template is instantiated by matchingstring
=basic_string<charT,traits,Allocator>
which resolves tocharT
=char
, etc.Because
std::reference_wrapper
(or any of its (zero) bases classes) cannot matchbasic_string<charT,traits,Allocator>
, the function declaration template cannot be instantiated into a function declaration, and cannot participate in overloading.What matters here is that there is no non-template
operator< (string, string)
prototype.Minimal code showing the problem
Gives:
Standard citations
14.8.2.1 Deducing template arguments from a function call [temp.deduct.call]
(...)
Note that this is the case with
std::string()<std::string()
.See comment below.
Comment
This implies that in this paragraph:
14.8.1 Explicit template argument specification [temp.arg.explicit]/6
the if should not be taken as a if and only if, as it would directly contradict the text quoted previously.