unexpected error in unordered_map iterator comparison - c++

I have a simple map std::map<string, my_namespace::MyType>, I am using c++11 so I replaced it with unordered_map for performance reasons. I got the following error when comparing an iterator with end().
auto cit = str_map_.find(str);
if (cit != str_map_.end()) {
...
}
In instantiation of 'bool my_namespace::operator!=(const T1&, const T2&) [with T1 = std::__detail::_Node_iterator<std::pair<const std::__cxx11::basic_string, my_namespace::MyType, false, true>; T2 = std::__detail::_Node_iterator<std::pair\ <const std::__cxx11::basic_string, my_namespace::MyType, false, true>]': no matching function ...
I debugged it down to my rather creative comparison operators for my_namespace::MyType:
template <class T>
struct MyType {
T* mt_;
};
struct MyTempClass {
std::string mtc_;
static int Compare(MyType<MyTempClass> const& lhs, MyType<MyTempClass> const& rhs) {
return lhs.mt_->mtc_.compare(rhs.mt_->mtc_);
}
static int Compare(std::string const& lhs, MyType<MyTempClass> const& rhs) {
return lhs.compare(rhs.mt_->mtc_);
}
static int Compare(MyType<MyTempClass> const& lhs, std::string const& rhs) {
return lhs.mt_->mtc_.compare(rhs);
}
};
template <class T1, class T2>
bool operator !=(T1 const& lhs, T2 const& rhs) {
int res = MyTempClass::Compare(lhs, rhs);
return (res != 0);
}
template <class T1, class T2>
bool operator ==(T1 const& lhs, T2 const& rhs) {
int res = MyTempClass::Compare(lhs, rhs);
return (res != 0);
}
static std::unordered_map<std::string, MyType<MyTempClass>> my_map;
But I am still puzzled why it did happen: the same code works fine with a plain map, and values type should not be involved in iterator comparisons?

You defined an operator!= overload that takes any type as an argument. That overload is in the same namespace as the type MyType. Therefore, it can potentially be found via ADL.
As the error message indicates, the std::unordered_map iterator used by the standard library is a class template specialization, specialized on the std::unordered_map template arguments. As such, when you compare iterators with !=, ADL is performed on the arguments and the namespaces searched by ADL also include the namespaces of type template arguments of the types of the arguments. Therefore, your operator!= overload in the namespace of MyType will also be found and participate in overload resolution.
Assuming you are using libstdc++ as standard library implementation based on the error message, you can have a look at it's implementation of the operator!= for hash table iterators and you will see that it uses a base class for these iterators, and defines the comparison operators for references to the base class objects.
As a consequence, the standard overload for the iterator comparison requires a derived-to-base reference conversion in its arguments, while your overload does not.
Therefore your overload is better and will be chosen to do the cit != str_map_.end() comparison. Your overload tries to pass the arguments MyTempClass::Compare which clearly doesn't work, because these functions don't expect std::unordered_map iterators.
The solution is not to overload operators for pairs of types that do not depend on user-defined types. Restrict your overloads to your own types:
template <class T1, class T2>
bool operator !=(MyType<T1> const& lhs, T2 const& rhs) {
int res = MyTempClass::Compare(lhs, rhs);
return (res != 0);
}
template <class T1, class T2>
bool operator !=(T1 const& lhs, MyType<T2> const& rhs) {
int res = MyTempClass::Compare(lhs, rhs);
return (res != 0);
}
(equivalently for operator==).
As far as I know, it is not forbidden to overload the operators for standard library type pairs, but I also don't think that the standard library is required to account for conflicts this generates as in your code.
With std::map the standard library implementation might have chosen a different way of implementing the iterator comparison, which made it a better fit in overload resolution or avoided that ADL finds your overload by not making the iterator a template specialized on the key/value type.

Related

Do custom iterators always need to explicitly specify value_type?

Since iterator is deprecated, I began converting iterators in my code base to use non-deprecated constructs. I could not seem to make my indirect iterator compliant with the std::forward_iterator concept unless I explicitly specified value_type. I would like to know if this is expected.
Based on the definition of iter_value_t and indirectly_readible_traits, it seems like there is no automatic inference of std::iter_value_t. Naively, I would have expected std::iter_value_t<Itr> to be defined as std::remove_cvref_t<std::iter_reference_t<Itr>> if no definition for value_type is present (which is checked via has-member-value-type in indirectly_readible_traits).
#include <vector>
template <std::forward_iterator Itr>
class IndirectItr {
public:
using value_type = std::iter_value_t<Itr>; // **do I need this?**
explicit IndirectItr(Itr itr = {}) : m_itr{itr} {}
bool operator==(const IndirectItr& rhs) const { return m_itr == rhs.m_itr; }
bool operator!=(const IndirectItr& rhs) const { return m_itr != rhs.m_itr; }
typename std::iter_reference_t<Itr> operator *() const { return *m_itr; }
IndirectItr& operator++() { ++m_itr; return *this; }
IndirectItr operator++(int) { auto ret = *this; ++(*this); return ret; }
typename std::iter_difference_t<Itr> operator-(const IndirectItr& rhs) const { return m_itr - rhs.m_itr; }
private:
Itr m_itr;
};
using Base = std::vector<int>::iterator;
static_assert(std::forward_iterator<IndirectItr<Base>>);
static_assert(std::same_as<std::iter_value_t<Base>, std::remove_cvref_t<std::iter_reference_t<Base>>>);
P.S. I have several indirect iterator definitions that wrap other iterators. The example above is representative of a custom indirect iterator. I don't have this exact class in my code.
You don't have to have a member value_type on your iterator. But your only alternative is to specialize iterator_traits<T> for your iterator type and provide a value_type alias there. So you may as well make it a member of the iterator.
The value_type cannot be computed from something else, as it may have no obvious relation to reference or any other operation on the iterator. This is one of the things that allows for proxy iterators, which pre-C++20 concepts did not.
std::forward_iterator includes std::input_iterator, which includes std::indirectly_readable, which contains:
requires(const In in) {
typename std::iter_value_t<In>;
typename std::iter_reference_t<In>;
typename std::iter_rvalue_reference_t<In>;
{ *in } -> std::same_as<std::iter_reference_t<In>>;
{ ranges::iter_move(in) } -> std::same_as<std::iter_rvalue_reference_t<In>>;
}
(where In is std::remove_cvref_t<IndirectItr<Base>>).
That typename std::iter_value_t<In>; line requires you to declare a value_type or to specialize std::iterator_traits<IndirectItr<Base>> (and provide value_type there), as explained here.
You cannot specialize std::iterator_traits<IndirectItr<T>> for all T (see also), so you can either pick the first and very reasonable option, or fully specialize for each IndirectItr you intend to use.

operator< redefinition on a std::pair<T1,T2> in class template

I've got the following situation in a class template case:
template<class T1,class T2>
class targetClass{
public:
typedef typename std::pair<T1, T2> ToSortType;
typedef typename std::set<ToSortType> ContainerSort;
void bar(ToSortType a, ToSortType b);
private:
ContainerSort container;
bool operator<(const ToSortType& rhs) const;
}
template<class T1,class T2>
void targetClass<T1,T2>::bar(ToSortType a, ToSortType b){
container.insert(a);
container.insert(b);
}
template <class T1,class T2>
bool targetClass<T1,T2>::operator<(const ToSortType& rhs) const
{
return this->first < rhs.first;
}
In main function something like this:
targetClass<int,T2> anObjectTarget;
T2 a;
T2 b;
anObjectTarget.bar(std::make_pair(0,a),std::make_pair(1,b));
Where T2 is a user-defined type which generally does not have a defined operator<
In this particular case, std::set has to compare std::pair<int,T2> by firstly check an operator< (and others, maybe) for int type and then for T2. In this case, the compiler can not find a suitable operator for T2. In the previous snippet then I make a redefinition of the operator concerned but the compiler complains in this way:
/usr/include/c++/7/bits/stl_pair.h:456: error: no match for ‘operator<’ (operand types are ‘const T2’ and ‘const T2’)
|| (!(__y.first < __x.first) && __x.second < __y.second); }
~~~~~~~~~~~^~~~~~~~~~~~
I've never redefined an operator before but looking to the documentation it looks correct to me (but not to the compiler).
The operator you overloaded is a member of targetClass<T1,T2> and takes two ToSortType as parameter. Thats not how an overload of the < operator works. Consider that for instances of class type the follwing two are equivalent:
a < b
a.operator<(b)
ie operators are just syntactic sugar for calling special member functions. The operator you wrote could only be called like
targetClass<T1,T2> t;
T1 a;
T2 b;
t.operator<(a,b);
but what the set tries to call is a < b, ie a.operator(b) and that apparently does not exist (std::pair<T1,T2> can only be comared via < when both T1 and T2 can).
Long story short: You cannot use your operator to compare two instances of ToSortType.
I would not recommend to try to overload the operator< for std::pair<T1,T2>, but rather use a custom type:
template<class T1,class T2>
class targetClass{
public:
struct value_type {
T1 first;
T2 second;
bool operator<(const value_type& other) {
return first < rhs.first;
}
}
using container_type = std::set<value_type>;
void bar(const value_type& a,const value_type& b);
private:
container_type container;
};
If you want to stay with std::pair then you can use the fact that std::set allows you to chose the type of the comparator. However, first I have to explain a bit not to confuse you because the following may appear to be contradicting the above (it does not). The default comparator that set uses is std::less<Key>, that is a type with an operator() that compares two element of type Key, it is something similar (but not exactly) like this:
template <typename Key>
struct less {
bool operator() (const Key& a,const Key& b) const {
return a < b;
}
};
And this is the place where the compiler cannot find a < for your Key type (which is std::pair<T1,T2>). You can use your own comparator:
template <typename T1,typename T2>
struct my_comparator {
bool operator() (const std::pair<T1,T2>& a, const std::pair<T1,T2>& b) const {
return a.first < b.first;
}
};
And then your set is
using container_type = std::set<typename std::pair<T1,T2>,typename my_comparator<T1,T2>>;

Could removing "using namespace std::rel_ops" change behavior?

I recently found that a large project has a "using namespace std::rel_ops;" in a frequently included header file, and in the global namespace. Ouch.
Specifically, it caused an issue because these two function template declarations are ambiguous:
namespace std::rel_ops {
template <class T>
bool operator!=(const T&, const T&);
}
namespace std {
template <class... TTypes, class... UTypes>
constexpr bool operator!=(const tuple<TTypes...>&, const tuple<UTypes...>&);
}
so my attempt to use an expression similar to std::tie(a.m1, a.m2, a.m3) != std::tie(b.m1, b.m2, b.m3) was ill-formed.
So the plan is to delete the using namespace std::rel_ops;, then fix the compiler errors which result, probably by defining more specific comparison operator functions. But I want to also go through the exercise of evaluating whether it would be possible this change could change the meaning of some code hidden somewhere else in this large project, without causing a compiler error.
In what conditions, if any, could two C++ programs with and without a directive using namespace std::rel_ops; differ in behavior, given that neither is ill-formed with a required diagnostic?
I suspect it would require another comparison operator function template which is less specialized than one in std::rel_ops, and which has different effective behavior from the std::rel_ops definition. Both conditions seem quite unlikely in a real project, and even less likely considered together.
There you have one example of this which tells moreless all about this class of errors. Depending on using namespace the program returns different values.
#include <utility>
class Type {
public:
Type(int) {}
};
bool operator==(Type, Type) { return false; }
template<class T , class U>
bool operator!=(const T& lhs, const U& rhs) {
return lhs == rhs;
}
using namespace std::rel_ops;
int main() {
Type bar(1);
Type baz(1);
return bar != baz;
}
Live example
Given a pair of programs with and without using namespace std::rel_ops; which don't violate a rule requiring a diagnostic, a difference in behavior can only be due to overload resolution between a member of rel_ops and another function or function template which is a worse overload in some context with both declarations viable. The overload resolution context could be:
a binary comparison expression, like E1 != E2
an explicit call using an operator-function-id as function name, like operator!=(E1, E2)
a use of operator-function-id or &operator-function-id as an initializer for a pointer to function or reference to function, in one of the contexts listed in [over.over]/1, like static_cast<bool(*)(const std::string&, const std::string&)>
It's actually not that hard for another function or function template specialization to be a worse overload than a member of std::rel_ops. Examples include:
The other function or template specialization requires a user-defined conversion.
class A {};
bool operator==(const A&, const A&);
class B {
public:
B(A);
};
bool operator==(const B&, const B&);
bool operator!=(const B&, const B&);
void test1() {
// With using-directive, selects std::rel_ops::operator!=<A>(const A&, const A&).
// Without, selects operator!=(const B&, const B&).
A{} != A{};
}
class C {
operator int() const;
};
bool operator==(const C&, const C&);
void test2() {
// With using-directive, selects std::rel_ops::operator!=<C>.
// Without, selects the built-in != via converting both arguments to int.
C{} != C{};
The other function or template specialization requires a derived-to-base "Conversion" ([over.best.ics]/6).
class D {};
bool operator==(const D&, const D&);
bool operator!=(const D&, const D&);
class E : public D {};
bool operator==(const E&, const E&);
void test3() {
// With using-directive, selects std::rel_ops::operator!=<E>.
// Without, selects operator!=(const D&, const D&).
E{} != E{};
}
The other function or template specialization has an rvalue reference parameter type.
class F {};
bool operator==(F&&, F&&);
void test4() {
// With using-directive, selects std::rel_ops::operator!=<F>.
// Without, selects operator!=(F&&, F&&).
F{} != F{};
}
The other function is a specialization of a less-specialized function template.
namespace N1 {
class A{};
bool operator==(const A&, const A&);
template <typename T1, typename T2>
bool operator!=(const T1&, const T2&);
}
void test5() {
// With using-directive, selects std::rel_ops::operator!=<N1::A>.
// Without, selects N1::operator!=<N1::A,N1::A>.
N1::A{} != N1::A{};
}
namespace N2 {
class B{};
bool operator==(const B&, const B&);
template <typename T>
bool operator!=(T&, T&);
}
void test6() {
// With using-directive, selects std::rel_ops::operator!=<N2::B>.
// Without, selects operator!=<const N2::B>.
const N2::B b1;
const N2::B b2;
b1 != b2;
}
Other categories and examples are possible, but that more than makes the point.
As far as practical concerns, it's unlikely any of the comparison operator names declared in std::rel_ops would be implemented to give results very different from the rel_ops definition for the same type, given that the related operator< or operator== is also defined. It might make a difference if "invalid" or special values have special treatment, like how for floating point types a <= b is not equivalent to !(b < a) when at least one operand is a NaN value. But the cases involving an implicit conversion to a different type could fairly easily result in different behavior. After a derived-to-base Conversion, any information in derived data members will very likely be ignored. After a converting constructor or conversion function, the comparison is on values of an entirely different type, which supposedly somehow represent the original arguments but might not represent their full functional identities.
(So for the original motivation, I decided it's worth using a static analysis tool to find all the places in the existing code naming a member of std::rel_ops, to help check for unintended changes in meaning not caught by just compiling.)

How to define boost::any operator ==

I want to define operator == for boost::any in my project. Since the arguments belong to the boost namespace, this is where argument-dependent lookup will search for it. So, the signature is:
namespace boost
{
bool operator == (const boost::any &, const boost::any &);
}
However, this generates ambiguous overload errors whenever I include a boost library that compares enums for equality, such as thread/locks.hpp — the compiler sees no reason to prefer converting the enums to int and using the built-in comparison instead of converting them to boost::any and using mine.
I can hack around this by also including any such libraries in the same file as my comparator and defining custom operators for comparing boost's enums. But there got to be a better way, right?
namespace boost {
template<class T,
typename std::enable_if<std::is_same<T, any>{}, bool>::type =true
>
bool operator == (const T& lhs, const T& rhs){
return any_equal(lhs, rhs);
}
}

Operators for non-primitive boxed types

I have the following class definition:
template <typename T>
class MyBox {
public:
MyBox(T value) { _value = value; }
operator T() const { return _value; }
private:
T _value;
};
typedef MyBox<int> MyInt;
typedef MyBox<std::string> MyString;
When I try to use operators on my typedefs like this
bool first = MyInt(1) == MyInt(1); // works
bool second = std::string(MyString("a")) == std::string(MyString("a")); //works
bool third = MyString("a") == MyString("a"); // does not compile
the compiler complains about the third comparison
no operator "==" matches these operands. operand types are: MyString == MyString
and this happens with any other non-primitve boxing (e.g. MyBox<float> works but MyBox<std::map<int,int> > not. Why is that so?
This is especially unclear to me because for the first and second comparison the operator T() is used - why can't that be done automatically for MyString as well?
UPDATE: Is there a simple solution to this other than providing the specific operators for each non-primitive template? And what to do with MyString("a") == std::string("a")?
The reasons on why it works for built-in types, but does't work for custom types is answered in the following SO quesiton: using user-defined conversions with implicit conversions in comparisons. In short, this is because type conversion does not happen for template-deduced types. And while built-in operator== for int is not a template (and thus can be found using type conversion when MyBox<int> is used), operator== for std::string is a template.
However, the question mentioned above doesn't have details on how to solve this problem. Here is how: add following free functions
template<class T>
bool operator==(const MyBox<T>& lhs, const MyBox<T>& rhs) {
return static_cast<const T&>(lhs) == static_cast<const T&>(rhs);
}
template<class T>
bool operator==(const MyBox<T>& lhs, const T& rhs) {
return static_cast<const T&>(lhs) == rhs;
}
template<class T>
bool operator==(const T& lhs, const MyBox<T>& rhs) {
return lhs == static_cast<const T&>(rhs);
}