In C++14, associative containers seem to have changed from C++11 – [associative.reqmts]/13 says:
The member function templates
find
,count
,lower_bound
,upper_bound
, andequal_range
shall not participate in overload resolution unless the typeCompare::is_transparent
exists.
What is the purpose of making an comparator "transparent"?
C++14 also provides library templates like this:
template <class T = void> struct less {
constexpr bool operator()(const T& x, const T& y) const;
typedef T first_argument_type;
typedef T second_argument_type;
typedef bool result_type;
};
template <> struct less<void> {
template <class T, class U> auto operator()(T&& t, U&& u) const
-> decltype(std::forward<T>(t) < std::forward<U>(u));
typedef *unspecified* is_transparent;
};
So for example, std::set<T, std::less<T>>
would not have a transparent comparator, but std::set<T, std::less<>>
would have one.
What problem does this solve, and does this change how standard containers work? For example, the template parameters of std::set
are still Key, Compare = std::less<Key>, ...
, so does the default set lose its find
, count
, etc. members?
In C++11 there are not member templates
find()
,lower_bound()
, etc. That is, nothing is lost by this change. The member templates were introduced with n3657 to allow heterogeneous keys being used with the associative containers. I don't see any concrete example where this is useful except for the example which is good and bad!The
is_transparent
use is intended to avoid unwanted conversions. If the member templates were unconstrained, existing code may pass through objects directly which would have been converted without the member templates. The example use-case from n3657 is locating an object in astd::set<std::string>
using a string literal: with the C++11 definition astd::string
object is constructed when passing a string literals to the corresponding member function. With the change it is possible to use the string literal directly. If the underlying comparison function object is implemented exclusively in terms ofstd::string
that is bad because now astd::string
would be created for each comparison. On the other hand, if the underlying comparison function object can take astd::string
and a string literal, that may avoid construction of a temporary object.The nested
is_transparent
type in the comparison function object provides a way to specify if the templated member function should be used: if the comparison function object can deal with heterogeneous arguments, it defines this type to indicate that it can deal with different arguments efficiently. For example, the new operator function objects just delegate tooperator<()
and claim to be transparent. That, at least, works forstd::string
which has overloaded less than operators takingchar const*
as argument. Since these function objects are also new, even if they do the wrong thing (i.e. require a conversion for some type) it would, at least, not be a silent change resulting in a performance degradation.Stephan T Lavavej talks about problems where the compiler keeps creating temporaries, and how his proposal of transparent operator functors will solve this in c++1y
GoingNative 2013 - Dont help the Compiler (at about the hour mark)
The following is all copy-pasta from n3657.
To quote Yakk,
and n3657,
n3421 provides an example of "Transparent Operator Functors".
The full code is here.
See Dietmar and remyabel's answer.
No, not by default.
The new member function template overloads of
find
etc. allow you to use a type that is comparable with the container's key, instead of using the key type itself. See N3465 by Joaquín Mª López Muñoz for rationale and a detailed, carefully written proposal to add this feature.At the Bristol meeting the LWG agreed that the heteregeneous lookup feature was useful and desirable, but we could not be sure that Joaquín's proposal would be safe in all cases. The N3465 proposal would have caused serious problems for some programs (see the Impact on existing code section). Joaquín prepared an updated draft proposal with some alternative implementations with different trade-offs, which was very useful helping the LWG understand the pros and cons, but they all risked breaking some programs in some way so there was no consensus to add the feature. We decided that although it wouldn't be safe to add the feature unconditionally, it would be safe if it was disabled by default and only "opt in".
The key difference of the N3657 proposal (which was a last-minute revision by myself and STL based on N3465 and a later unpublished draft by Joaquín) was to add the
is_transparent
type as the protocol that can be used to opt in to the new functionality.If you don't use a "transparent functor" (i.e. one that defines a
is_transparent
type) then the containers behave the same as they've always done, and that's still the default.Iff you choose to use
std::less<>
(which is new for C++14) or another "transparent functor" type then you get the new functionality.Using
std::less<>
is easy with alias templates:The name
is_transparent
comes from STL's N3421 which added the "diamond operators" to C++14. A "transparent functor" is one which accepts any argument types (which don't have to be the same) and simply forwards those arguments to another operator. Such a functor happens to be exactly what you want for heterogeneous lookup in associative containers, so the typeis_transparent
was added to all the diamond operators and used as the tag type to indicate the new functionality should be enabled in associative containers. Technically, the containers don't need a "transparent functor", just one that supports calling it with heterogeneous types (e.g. thepointer_comp
type in https://stackoverflow.com/a/18940595/981959 is not transparent according to STL's definition, but definingpointer_comp::is_transparent
allows it to be used to solve the problem). If you only ever lookup in yourstd::set<T, C>
with keys of typeT
orint
thenC
only needs to be callable with arguments of typeT
andint
(in either order), it doesn't need to be truly transparent. We used that name partly because we couldn't come up with a better name (I would have preferredis_polymorphic
because such functors use static polymorphism, but there's already astd::is_polymorphic
type trait which refers to dynamic polymorphism).