Concept of "equal" in C++20 concepts - c++

I have found multiple times, while reading some concepts definitions, the use of the term equal, like in Swappable:
Let t1 and t2 be equality-preserving expressions that denote distinct equal objects of type T,
Is equal defined somewhere in the standard? I guess it means that the semantics of two objects, or the value they refer (the human semantics given to their represented domain value) are the same, even if the objects are not comparable (no operator== overloaded), or something abstract like that (like, two objects a and b are equal if a == b would yield true assuming it is a valid expression --for example, because operator== is not defined because it's not required).

Since templates are going to work on the semantics the user gives, the concept of equality is largely defined by the program. For example, if I am working with case insensitive strings, I can consider the strings FoO and fOo to be equal and FoO and bAr to be unequal. The operations I supply must reflect this semantics.
Equality isn't defined based on operator==; on the contrary, operator== is (in a sense) defined based on equality. [concept.equalitycomparable]/equality_comparable:
template<class T>
concept equality_comparable = weakly-equality-comparable-with<T, T>;
Let a and b be objects of type T. T models
equality_­comparable only if bool(a == b) is true when a is
equal to b ([concepts.equality]), and false otherwise.
[ Note: The requirement that the expression a == b is
equality-preserving implies that == is transitive and symmetric.
— end note ]

Related

Does greater operator ">" satisfy strict weak ordering?

Definition:
Let < be a binary relation where a < b means "a is less than b".
Let > be a binary relation where a > b means "a is greater than b".
So, we assume < and > have meanings we usually use in a daily life. Though, in some programming languages (e.g. C++), we can overload them to give them different definitions, hereafter we don't think about that.
Context:
As far I read mathematical definition of strict weak ordering (e.g. Wikipedia), I think both < and > satify it. However, all examples I saw in many websites refer only to <. There is even a website which says
what they roughly mean is that a Strict Weak Ordering has to behave the way that "less than" behaves: if a is less than b then b is not less than a, if a is less than b and b is less than c then a is less than c, and so on.
Also, in N4140 (C++14 International Standard), strict weak ordering is defines as
(§25.4-4) If we define equiv(a, b) as !comp(a, b) && !comp(b, a), then the requirements are that comp and equiv both be transitive relations
where comp is defined as
(§25.4-2) Compare is a function object type (20.9). The return value of the function call operation applied to an object of type Compare, when contextually converted to bool (Clause 4), yields true if the first argument of the call is less than the second, and false otherwise. Compare comp is used throughout for algorithms
assuming an ordering relation.
Question:
Does ">" satisfy strict weak ordering? I expect so, but have no confidence.
Does greater operator “>” satisfy strict weak ordering?
The mathematical strict greater than relation is a strict weak ordering.
As for the operator in C++ langauge: For all integers types: Yes. In general: No, but in most cases yes. Same applies to strict less than operator.
As for the confusing quote, "is less than" in that context intends to convey that means that the the end result of the sort operation is a non-decreasing sequence i.e. objects are "less" or equal to objects after them. If std::greater is used as comparison object, then greater values are "lesser" in order.
This may be confusing, but is not intended to exclude strict greater than operator.
what is the case where > doesn't satisfy strict weak ordering?
Some examples:
Overloaded operators that don't satisfy the properties.
> operator on pointers that do not point to the same array has unspecified result.
> does not satisfy irreflexivity requirement for floating point types in IEEE-754 representation unless NaNs are excluded from the domain.
Even if the standard refers to "less than" for arbitrary Compare functions, that only implies "less than" in the context of the ordering.
If I define an ordering by comparison function [](int a, int b) { return a > b; }, then an element is "less than" another in this ordering if its integer value is greater. That's because the ordering I've created is an ordering of the integers in reverse order. You shouldn't read < as "less than" in orderings. You should read it as "comes before".
Whenever x < y is a strict weak ordering then x > y is also a strict weak ordering, just with the reverse order.

std::unordered_set::equal_range iterator question

std::unordered_set::equal_range returns a pair of iterators describing the range of values in the set where the keys for the values compare as equal. Given:
auto iteratorFromEqualRange = someUnorderedSet.equal_range(key).first;
auto iteratoFromFind = someUnorderedSet.find(key);
is it guaranteed by the Standard that:
++iteratorFromEqualRange == ++iteratorFromFind;
as they are both defined in terms of std::unordered_set::iterator? In other words, can a different implementation of std::unordered_set keep "hidden" information about the context of what we're iterating, or is this a not-very-subtle enforcement of the bucket interface (which limits our implementation options)?
I expect that this is indeed a guarantee, given the requirements of LegacyForwardIterator, I'm just asking for confirmation (or better news that includes some kind of escape hatch)
The iterator of unordered_set is a Forward Iterator (now named LegacyForwardIterator).
The C++14 standard (final draft n4140) states this regarding Forward Iterators:
24.2.5 Forward iterators [forward.iterators]
1 A class or pointer type X satisfies the requirements of a forward iterator if
...
(1.5) — objects of type X offer the multi-pass guarantee, described below.
...
3 Two dereferenceable iterators a and b of type X offer the multi-pass guarantee if:
(3.1) — a == b implies ++a == ++b and
(3.2) — X is a pointer type or the expression (void)++X(a), *a is equivalent to the expression *a.
Combining (1.5) and (3.1) in this case would mean that ++iteratorFromEqualRange == ++iteratorFromFind; is guaranteed by the standard, provided both these iterators can be dereferenced.

Are conditional expressions in C++ always of bool type?

In C conditional-oriented operators evaluate to either 1 or 0 of type int (even if it does have dedicated _Bool type). Referring to C11 N1570 draft:
C11 §6.5.8/6 Relational operators
Each of the operators < (less than), > (greater than), <= (less than
or equal to), and >= (greater than or equal to) shall yield 1 if the
specified relation is true and 0 if it is false.107) The result has
type int.
C11 §6.5.9/3 Equality operators
The == (equal to) and != (not equal to) operators are analogous to the
relational operators except for their lower precedence.108) Each of
the operators yields 1 if the specified relation is true and 0 if it
is false. The result has type int. For any pair of operands, exactly
one of the relations is true.
C11 6.5.13/3 Logical AND operator
The && operator shall yield 1 if both of its operands compare unequal
to 0; otherwise, it yields 0. The result has type int.
C11 6.5.14/3 Logical OR operator
The || operator shall yield 1 if either of its operands compare
unequal to 0; otherwise, it yields 0. The result has type int.
As I checked C++ seems to be different in this matter, as in following example (see http://ideone.com/u3NxfW):
#include <iostream>
#include <typeinfo>
int main() {
double x = 10.0;
std::cout << typeid(x <= 10.0).name() << std::endl;
return 0;
}
outputs b, which as I guess indicates bool type. Does C++ guarantee that all of these operators always evaluate to bool type (in contrast to C)?
No, because of operator overloading. This has been mentioned before, but I can give the real life example of expression templates. The idea, generally, is to allow writing "lazy" expressions (that is, really, function objects or ASTs) with syntax that is very similar to the normal, eager use of logical operators. Typically, many other operators, in particular arithmetic operators are also, overloaded.
For instance, one design goal of Boost.Lambda was to simplify the use of algorithms:
std::string str;
// ...
std:.string::iterator firstA = std::find_if(str.begin(), str.end(), _1 == 'a' || _1 == 'A');
Previously, in "pure" C++98, it was generally necessary to write numerous named functions or function objects before many standard algorithms could be used effectively.
Since C++11, Boost.Lambda is not as useful any more since lambda expressions have been added to the core language. There are still numerous EDSLs (embedded domain-specific languages) where C++11 lambdas cannot replace expression templates though, e.g. you may want to generate SQL command strings directly from a C++ EDSL in a way similar to LINQ in .NET, but as a portable library solution. Another example: the VexCL library uses expression templates to generate GPU kernels.
This is probably the only legitimate use of non-bool return types for overloaded logical operators, but it's not generally considered esoteric.
Contrary to C, in C++, relational operators, equality operators and logical operators (logical AND, logical OR and logical negation) all yield a value of type bool.
For example:
(C++11, 5.9p1 Relational operators) "[...] The type of the result is bool."
EDIT: for the sake of completeness, all the operators listed above can be overloaded and thus the resulting type can be changed. See Arne Vogel answer for real life example.

Comparison semantics with std::atomic types

I'm trying to find where the comparison semantics for the type T with std::atomic is defined.
I know that beside the builtin specializations for integral types, T can be any TriviallyCopyable type. But how do operations like compare_and_exchange_X know how to compare an instance of T?
I imagine they must simply do a byte by byte comparison of the user defined object (like a memcmp) but I don't see where in the standard this is explicitly mentioned.
So, suppose I have:
struct foo
{
std::uint64_t x;
std::uint64_t y;
};
How does the compiler know how to compare two std::atomic<foo> instances when I call std::atomic<foo>::compare_and_exchange_weak()?
In draft n3936, memcmp semantics are explicitly described in section 29.6.5.
Note: For example, the effect of atomic_compare_exchange_strong is
if (memcmp(object, expected, sizeof(*object)) == 0)
memcpy(object, &desired, sizeof(*object));
else
memcpy(expected, object, sizeof(*object));
and
Note: The memcpy and memcmp semantics of the compare-and-exchange operations may result in failed comparisons for values that compare equal with operator== if the underlying type has padding bits, trap bits, or alternate representations of the same value.
That wording has been present at least since n3485.
Note that only memcmp(p1, p2, sizeof(T)) != 0 is meaningful to compare_and_exchange_weak (failure guaranteed). memcmp(p1, p2, sizeof(T)) == 0 allows but does not guarantee success.
It's implementation defined. It could just be using a mutex lock or it could be using some intrinsics on memory blobs. The standard simply defines it such that the latter might work as an implementation strategy.
The compiler doesn't know anything here. It'll all be in the library. Since it's a template you can go read how your implementation does it.

dynamic_cast confusion

I give up on this...
$5.2.7/2- "If T is a pointer type, v
shall be an rvalue of a pointer to
complete class type, and the result is
an rvalue of type T. If T is a
reference type, v shall be an lvalue
of a complete class type, and the
result is an lvalue of the type
referred to by T."
In accordance with the above, the following code should be well-formed.
struct A{};
struct B : A{};
int main(){
B b;
A a, &ar1 = b;
B& rb1 = dynamic_cast<B&>(ar1); // Does not $5.2.7/2 apply here?
B& rb2 = dynamic_cast<B&>(a); // and also here?
}
But it is not. All compilers complain about the operand to dynamic_cast not being polymorphic in accordance with
$5.2.7/6- Otherwise, v shall be a
pointer to or an lvalue of a
polymorphic type (10.3).
So my question is what does $5.2.7/2 mean? Why does $5.2.7/6 kick in here?
Well, all requirements in 5.2.7 should be observed together. You can't just stop after 5.2.7/2 and start writing code that supposedly satisfies everything "up to 5.2.7/2". The entire 5.2.7 defines the specification of dynamic_cast.
The polymorphic requirement is singled out because it is conditional. When you use dynamic_cast for upcasts, the polymorphic requirement does not apply (in fact, dynamic_cast is equivalent to static_cast in upcasts). The polymorphic requirement only applies when you use dynamic_cast for downcasts or crosscasts.
The specification of dynamic_cast is organized sequentially, meaning that it takes care of simpler cases first, and then proceeds to more complicated applications. You are supposed to read it step by step, until it covers your specific situation. Everything you read along that path applies cumulatively, And "otherwise" means: "if we haven't covered your case yet, then continue reading".
In order to do a downcast as in your example, Struct A needs to be polymorphic, and have RTTI. Here's an adjusted version that works, to a point:
struct A{virtual void f(){}};
struct B : A{};
int main(){
B b;
A a, &ar1 = b;
B& rb1 = dynamic_cast<B&>(ar1); // Does not $5.2.7/2 apply here?
//B& rb2 = dynamic_cast<B&>(a); // and also here?
}
By adding a virtual making it polymorphic, RTTI is enabled for the class, allowing downcasts.
Note that your second example cannot work - since you are casting a pod (a) to a reference to a pod - which is not allowed.
Update:
Your code is not allowed under 5.2.7.5 and is neither allowed under 5.2.7.6. My adjustment makes it work under 5.2.7.6
"Otherwise" in this case means, "unless the conditions in 5.2.7/5 apply".
You can tell this because /2 places a requirement on the program regarding the operand of the the dynamic_cast (note the "shall" language of "v shall be an lvalue" vs. the "is" language of "the result is an lvalue"). In common with other places in the standard, expressing a requirement doesn't necessarily mean that it's the only requirement. Other clauses can state extra requirements. In this case, /6 states an extra requirement that only applies in certain cases, depending on T and the static type of v.
/3, /4, /5 are telling you about the value of the result, and they're entirely consistent with the requirement in /2. None of them starts with "Otherwise". So to me it's fairly obvious that they do not form an "else if" chain starting at /2.
Some brackets or something might make this clearer (i.e. that the "otherwise" in /6 applies to the "if" in /5, and not to the "if" in /2, /3, or /4). But that's just not the house style.
Aside from anything else, the "otherwise" in /5 logically cannot meaningfully apply to the conditions in /2. /1 says that T must be "pointer or reference to complete class type, or cv void* ". /2 covers two cases - pointer types, and reference types. That's everything. There is no "otherwise" to /2 (unless it were to say "otherwise, a conforming compiler must issue a diagnostic", but that's implicit)