# Spaceship Operator And Argument-Dependent Lookup Explained
## Spaceship Operator
In C++ you can define your own comparison operators, but there is also a way to do all the comparisons with one of them which is the spaceship operator. Comparisons depend on the type of the object, and sometimes we need to use functions that only the object has. That's where ADL is needed.
The spaceship operator, also known as the three-way comparison operator, is a binary operator used in some programming languages to perform a three-way comparison between two values. It is typically represented by the symbol <=>. The result of the comparison can be used to determine whether one value is less than, equal to, or greater than another.
When the spaceship operator is used then it will return one of four enums:
- equivalent
- greater
- less
- unordered -> only when the comparison isn't clear
## Spaceship Operator Ordering
To understand the spaceship operator we first need to understand how the ordering of elements goes. There are different types of ordering like:
- Strong ordering
- Weak ordering
- Partial ordering
The next section shows the result of the <=> operator implemented in a way to demonstrate the differences of the ordering types.
### Strong Ordering
Strong ordering exists when a set of elements can be unambiguously arranged in a specific sequence based on a well-defined comparison criterion. In a strongly ordered set, any two distinct elements can be compared, and the result is always deterministic—indicating whether one element is less than, equal to, or greater than the other. This clear and consistent arrangement ensures predictability and reliability when working with ordered collections or data structures.
For example there are 2 objects of type int which are named a and b. We would like to know if a is greater than, less than or equal to b.
```c++
std::string comparing_integer_strong_ordering(int a, int b){
// A simple if-clause to check integers and wether a is equal to, greater than or less than b
if(a == b){
return "equal to";
}
else if (a > b){
return "greater than";
}
else{
return "less than";
}
}
int main(){
int a = 5;
int b = 6;
// Expected result: "less than"
std::cout << "Strong ordering comparison with integers: a is "
<< comparing_integer_strong_ordering(a,b) << " b" << std::endl;
}
```
Result:
```
Strong ordering comparison with integers: a is less than b
```
In the code above, the function comparing_integer_strong_ordering takes two integer parameters, a and b, and compares them using a strong ordering logic. The function returns a string indicating whether a is equal to, greater than, or less than b.
The main function initializes two integers, a and b, with values 5 and 6, respectively. It then calls the comparing_integer_strong_ordering function and prints the result, which is the relationship between a and b based on the strong ordering comparison. In this specific example, the expected result is "less," as a (5) is less than b (6).
### Weak Ordering
Weak ordering is a little different in comparison to strong ordering. When using weak ordering it also returns either greater(>), less than(<) or equivalent. The main difference between weak and strong ordering is that two different objects can be considered the same even though they are different.
As an example there are 2 object of type string a and b which both store the same word "apple" one is stored with an uppercase "A" and one with a lowercase "a".
```c++
std::string comparing_strings_weak_ordering(std::string a, std::string b){
// Using std::transform to change the strings to have lowercase charachters
std::transform(a.begin(), a.end(), a.begin(),[](unsigned char c){ return std::tolower(c);});
std::transform(b.begin(), b.end(), b.begin(),[](unsigned char c){ return std::tolower(c);});
// Using the "strcmp" to compare the strings given
int result = std::strcmp(a.c_str(),b.c_str());
if(result == 0){
return "equal to";
}
else if(result > 0){
return "greater than";
}
else{
return "less than";
}
}
int main(){
std::string a_string = "apple";
std::string b_string = "Apple";
// Expected result: "equal to"
std:: cout << "Weak ordering comparison with strings: a_string is ";
std::cout << comparing_strings_weak_ordering(a_string, b_string) << " b_string" << std::endl;
}
```
Result:
```
Weak ordering comparison with strings: a_string is equal to b_string
```
In this code, the function comparing_strings_weak_ordering takes two std::string parameters, a and b. The function first converts both strings to lowercase using std::transform and std::tolower to ensure a case-insensitive comparison. Then, it uses the C library function std::strcmp to compare the two strings. The result of the comparison is stored in the variable result. Finally, based on the value of result, the function returns a string indicating whether the strings are equal, greater than, or less than each other.
In the main function, two strings, a_string and b_string, are initialized with values "apple" and "Apple," respectively. The comparing_strings_weak_ordering function is called with these strings, and the expected result is "equal to" since the comparison is case-insensitive, and the transformed strings are the same. The result is then printed to the console.
### Partial Ordering
Partial ordering occurs when not all elements in a set are comparable. In a partially ordered set, some elements may be comparable, providing a clear ordering relationship, while others might lack a defined relationship. This flexible arrangement allows for a partial hierarchy, with certain pairs of elements having an order, while others remain unordered or incomparable.
For example there are 2 objects of type std::tuple which are named a_tuple and b_tuple. We would like to know if a_tuple is greater than, less than or equal to b_tuple.
```c++
std::string comparing_tuples_partial_ordering(std::tuple<int,int> a, std::tuple<int,int> b){
/*
* Before we do any comparing we have to check if there is the same int in the same spot of the two tuples
* We do that in the first if-else statement
*/
// This is the case where the first int of the two tuples is the same
if(std::get<0>(a) == std::get<0>(b)){
// In this if-else statement we compare the second integer of the tuples
if (std::get<1>(a) < std::get<1>(b))
return "less than";
else if (std::get<1>(a) > std::get<1>(b))
return "greater than";
else if (std::get<1>(a) == std::get<1>(b))
return "equal to";
}
// This is the case where the second int of the two tuples is the same
else if(std::get<1>(a) == std::get<1>(b)){
// In this if-else statement we compare the first integer of the tuples
if (std::get<0>(a) < std::get<0>(b))
return "less than";
else if (std::get<0>(a) > std::get<0>(b))
return "greater than";
else if (std::get<0>(a) == std::get<0>(b))
return "equal to";
}
/*
* If none of the numbers are the same we can't do any comparison and we return undefined
* When you cant define a return statement for comparison then that means that the
* compare function has partial ordering
*/
return "undefined";
}
int main(){
// Tuples to show partial order comparison
std::tuple<int,int> a_tuple = {1,2};
std::tuple<int,int> b_tuple = {3,4};
// You can see that the output is undefined
std:: cout << "Partial ordering comparison with tuples: a_tuple is ";
std::cout << comparing_tuples_partial_ordering(a_tuple,b_tuple) << " b_tuple" << std::endl;
a_tuple = {1,2};
b_tuple = {1,4};
// Here you can see that we can compare because the tuples have the same integer in the same spot
std:: cout << "Partial ordering comparison with tuples: a_tuple is ";
std::cout << comparing_tuples_partial_ordering(a_tuple,b_tuple) << " b_tuple" << std::endl;
}
```
Result:
```
Partial ordering comparison with tuples: a_tuple is undefined b_tuple
Partial ordering comparison with tuples: a_tuple is less than b_tuple
```
In this code, the function comparing_tuples_partial_ordering takes two tuples, a and b, each containing tuples. The function first checks if the first integers in the tuples are the same. If they are, it proceeds to compare the second integers to determine whether a is less than, equal to, or greater than b. If the first integers are different, it checks if the second integers are the same and compares the first integers in the same way as mentioned above. If neither condition is met, meaning the tuples have no common integers in the same positions, the function returns "undefined," indicating that a comparison cannot be made, demonstrating partial ordering.
In the main function, two tuples, a_tuple and b_tuple, are initialized with values {1,2} and {3,4}. Since these tuples have no common integers in the same positions, the result of the first comparison is "undefined." In the second part of the main function, a_tuple and b_tuple are updated to {1,2} and {1,4}, respectively. Now, the tuples have a common integer (1) in the same position, allowing for a partial ordering comparison. The result is then printed to the console.
### Compilers results
You can also use the already implemented spaceship operator to do the same comparisons. There are some differences in the results. For example if we try to compare the same tuples as earlier with the implemented spaceship operator the result will be that a_tuple is less than b_tuple.
```c++
int main() {
// Tuples to show partial order comparison from the compiler
std::tuple<int,int> a_tuple = {1,2};
std::tuple<int,int> b_tuple = {3,4};
// Here we store the result of the compilers comparison of two tuples
std::partial_ordering c_tuple_result = c_a_tuple <=> c_b_tuple;
std::cout << "Compilers result for comparing tuples with partial ordering: ";
if (c_tuple_result == std::partial_ordering::less) {
std::cout << "Less" << std::endl;
} else if (c_tuple_result == std::partial_ordering::equivalent) {
std::cout << "Equal" << std::endl;
} else if (c_tuple_result == std::partial_ordering::greater){
std::cout << "Greater" << std::endl;
} else if (c_tuple_result == std::partial_ordering::unordered){
std::cout << "Unordered" << std::endl;
}
}
```
Result:
```
Compilers result for comparing tuples with partial ordering: Less
```
This happens because the spaceship operator for comparing tuples is implemented in a different way compared to the one showed earlier. The one showed earlier was created like that only to demonstrate partial ordering, but there is an actual way of creating strong ordering for comparing tuples also, which is what the compiler uses.
## Argument-Dependent Lookup
Argument-dependent lookup (ADL), also known as "argument-dependent name lookup" or "Koenig lookup," is a feature in the C++ programming language that affects how the compiler searches for the names of functions and operators in expressions. ADL is particularly relevant when dealing with function overloading and namespaces.
Here's a brief explanation of how ADL works:
Normal Name Lookup:
- The compiler first looks for the function or operator in the scope where it is called.
- If the name is not found in that scope, the compiler looks in outer scopes and in the global scope.
Argument-Dependent Lookup (ADL):
- If the function or operator is not found through normal name lookup, ADL includes the namespaces associated with the types of the function arguments.
- The compiler looks for the function or operator in the namespaces of the function arguments.
Here is an example to demonstrate ADL:
```c++
#include <iostream>
namespace A {
struct MyType {};
void foo(MyType mt) {
std::cout << "A::foo called" << std::endl;
}
}
namespace B {
void foo(int i) {
std::cout << "B::foo called" << std::endl;
}
}
int main() {
A::MyType obj;
foo(obj); // Calls A::foo due to ADL, even though 'foo' is not in the scope.
return 0;
}
```
In this example, the foo function is called with an argument of type A::MyType. Even though foo is not in the same scope as the function call, ADL allows the compiler to find the function in the namespace A, where the argument type is declared. Therefore, A::foo is called.
ADL is particularly useful in generic programming scenarios where the types are not known in advance. It allows functions to be found in the namespaces associated with the argument types, providing a way to extend functionality without modifying existing code.
## References
geeksforgeeks:
- https://www.geeksforgeeks.org/3-way-comparison-operator-space-ship-operator-in-c-20/
cppreference:
- https://en.cppreference.com/w/cpp/language/default_comparisons
- https://en.cppreference.com/w/cpp/language/adl