<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Infotraining Dev Blog]]></title><description><![CDATA[Modern C++ explained clearly. Tutorials, patterns, and deep technical explorations designed to help developers write faster, safer, and more expressive code.]]></description><link>https://blog.infotraining.pl</link><image><url>https://cdn.hashnode.com/uploads/logos/672cb2fad0d709d7ab20eec0/d5022b9e-113b-4af9-8c72-22545d4ca9a8.png</url><title>Infotraining Dev Blog</title><link>https://blog.infotraining.pl</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 07 Apr 2026 15:58:42 GMT</lastBuildDate><atom:link href="https://blog.infotraining.pl/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Hashing in C++26]]></title><description><![CDATA[In my previous post, I showed a generic approach to hashing (see https://blog.infotraining.pl/how-to-hash-objects-without-repetition). That implementation required a tied() member that exposes an obje]]></description><link>https://blog.infotraining.pl/hashing-in-cpp-26</link><guid isPermaLink="true">https://blog.infotraining.pl/hashing-in-cpp-26</guid><category><![CDATA[Cpp26]]></category><category><![CDATA[reflection]]></category><dc:creator><![CDATA[Krystian Piękoś]]></dc:creator><pubDate>Tue, 07 Apr 2026 09:27:15 GMT</pubDate><content:encoded><![CDATA[<p>In my previous post, I showed a generic approach to hashing (see <a href="https://blog.infotraining.pl/how-to-hash-objects-without-repetition">https://blog.infotraining.pl/how-to-hash-objects-without-repetition</a>). That implementation required a <code>tied()</code> member that exposes an object's members as an <code>std::tuple</code> of references. With such a <code>tied()</code> implementation, we could provide a specialization of <code>std::hash&lt;T&gt;</code> to compute an object's hash.</p>
<p>A type could opt in to this generic hashing in two ways:</p>
<ul>
<li><p>inheriting from the <code>HashableForTiedMembers</code> tag struct,</p>
</li>
<li><p>or defining a <code>constexpr</code> template variable that enables the concept used by the <code>std::hash&lt;T&gt;</code> specialization.</p>
</li>
</ul>
<p>This design works well in C++20 and C++23. With C++26 (and GCC 16), however, language-level reflection lets us implement the same functionality without <code>tied()</code>, enabling a simpler and more robust reflection-based solution.</p>
<h2>Introduction to C++ Reflection</h2>
<p>C++26 introduces a long‑awaited feature that changes how we do metaprogramming. Reflection lets us inspect — and in some cases manipulate — program structure at compile time, simplifying tasks such as generic hashing, serialization, and code generation.</p>
<h3>Reflection operator</h3>
<p>The reflection operator <code>^^</code> is used to obtain a compile-time representation of an entity. It can be applied to various constructs such as variables, functions, types, namespaces, and enumerators. The result of applying the reflection operator is a reflection-value that contains information about the entity, such as its members, member names, types, and attributes. This value can be stored in a variable of opaque type <code>std::meta::info</code> and can be queried at compile-time using various reflection queries - <strong>meta-functions</strong> (defined in <code>&lt;meta&gt;</code> header).</p>
<pre><code class="language-cpp">struct Data { int id; std::string name };

std::meta::info r_type = ^^Data;
std::meta::info r_id_member = ^^Data::id;
</code></pre>
<p>Information obtained through reflection can be used for performing compile-time checks, generating code, or implementing custom behavior based on the structure of the code. The reflection value can be used to produce grammatical elements using so-called <strong>splicers</strong> with <code>[: reflection-value :]</code> syntax, this allows to generate code based on the structure of the code at compile time.</p>
<pre><code class="language-cpp">Data data{42, "forty-two"};

assert(data.[: r_id_member :] == 42); // the same as: data.id == 42
</code></pre>
<h3>Simple example</h3>
<p>Let's see a simple example that allows us to get member values using indexes:</p>
<pre><code class="language-cpp">#include &lt;meta&gt;
#include &lt;string&gt;

struct Person {
    int id;
    std::string name;
};

consteval std::meta::info member_number(int n) {
  if (n == 0) 
    return ^^Person::id; 
  else if (n == 1) 
    return ^^Person::name; 

  std::unreachable();
}

int main() {
  Person p{42, "John Doe"};

  p.[: member_number(0) :] = 665;  // Same as: p.id = 42;
  p.[: member_number(1) :] = "John Smith"; // Same as: p.name = "John Smith";
  p.[: member_number(5) :] = "John Doe";   // Error: member_number(5) is not a constant
}
</code></pre>
<p>In the example above, we define a struct Person with two members: <code>id</code> and <code>name</code>. We then create a consteval function, <code>member_number</code>, which takes an integer <code>n</code> and returns the reflection of the corresponding member of <code>Person</code>. This enables us to associate indexes with the struct's members. The <code>consteval</code> keyword ensures the function is evaluated at compile time, as reflection values can only be used in compile-time contexts.</p>
<p>In the <code>main</code> function, we create an instance of <code>Person</code> and use the splicer syntax to assign values to the members based on their index.</p>
<p>There's no need to manually index struct items. We can utilize the standard library's meta-function <code>std::meta::nonstatic_data_members_of(^^T, context)</code>. This function accepts a reflected value of a type <code>^^Person</code>) and returns a <code>std::vector</code> of the type's reflected non-static data members, allowing easy iteration or indexing.</p>
<p>Simplified implementation of <code>member_number()</code> function can look like this:</p>
<pre><code class="language-c++">using namespace std::meta;

consteval info member_number(int n) {
  auto ctx = access_context::current();
  return nonstatic_data_members_of(^^Person, ctx)[n];
}
</code></pre>
<p>Another important aspect is the use of <code>std::meta::access_context::current()</code> to obtain the current access context. This is crucial because the meta-function must determine which entities are visible and accessible at the reflection point. The access context provides details about the scope and visibility of entities, allowing us to accurately reflect on the members of <code>Person</code>. If we need to reflect on private members, we can use <code>std::meta::access_context::unchecked()</code>.</p>
<h2>Hashing with Reflection</h2>
<p>After this brief introduction to C++26 reflection, let's explore how it can be used to implement hashing for our types. In C++, a hashable type is one for which a specialization of the <code>std::hash</code> template exists. This specialization specifies how to compute a hash value for an object of that type.</p>
<pre><code class="language-cpp">template &lt;&gt;
struct std::hash&lt;Person&gt; {
    size_t operator()(const Person&amp; p) const {
        size_t seed = 0;
        Utility::hash_combine(seed, p.id);   // Hashing an integer
        Utility::hash_combine(seed, p.name); // Hashing a string
        return seed;
    }
};
</code></pre>
<p>Before C++26, we had to either write our own specialization of <code>std::hash</code> for each type we wanted to hash or use a generic approach that employed <code>std::tie()</code> (or similar methods) to combine the hash values of individual members. I discussed the latter approach in my previous article. Now, we can implement a more elegant and efficient solution using reflection.</p>
<p>To begin, let's define a concept called <code>Hashable</code>. This concept will verify if a type is hashable by determining whether a specialization of <code>std::hash</code> exists for that type.</p>
<pre><code class="language-cpp">template &lt;typename T&gt;
concept Hashable = requires {
    { std::hash&lt;T&gt;{}(std::declval&lt;T&gt;()) } -&gt; std::convertible_to&lt;size_t&gt;;
};

static_assert(Hashable&lt;int&gt;); // int is hashable
static_assert(Hashable&lt;std::string&gt;); // std::string is hashable
static_assert(Hashable&lt;Person&gt;); // Person is hashable
</code></pre>
<p>Next, we can create a generic hash function that utilizes reflection to compute the hash value for any hashable type. This function will iterate over the non-static data members of the type and combine their hash values using <code>Utility::hash_combine()</code> or a similar function like <code>boost::hash_combine()</code>.</p>
<pre><code class="language-cpp">template &lt;typename T&gt;
    requires std::is_class_v&lt;T&gt;
size_t calculate_hash(const T &amp;obj, size_t seed = 0)
{
    constexpr auto ctx = std::meta::access_context::unchecked();
    
    static constexpr auto r_data_members = 
        std::define_static_array(
            std::meta::nonstatic_data_members_of( ^^T, ctx)
        );

    // iteration over non-static data members
    template for (constexpr auto r_dm : r_data_members)
    {
        const auto&amp; member_value = obj.[: r_dm :];
        Utility::hash_combine(seed, member_value);
    }

    return seed;
}
</code></pre>
<p>In this implementation, we first verify if the type <code>T</code> is a class or struct using <code>std::is_class_v&lt;T&gt;</code> trait. We then retrieve the non-static data members of the type with <code>std::meta::nonstatic_data_members_of( ^^T, ctx)</code>, where <code>ctx</code> serves as the access context. To iterate over public, protected, and private members, we use <code>std::meta::access_context::unchecked()</code>, which grants access to all members regardless of their access specifier. Since <code>std::meta::nonstatic_data_members_of</code> returns a <code>std::vector</code>, we define a static array, <code>r_data_members</code>, to store these reflected data members. This setup allows us to employ a template for loop to iterate over the members at compile time. The standard library's <code>std::define_static_array()</code> helps bridge compile-time and runtime by defining a static array from a compile-time sequence. Using a template for loop, we iterate over the reflected data members. For each member, we access its value for the given object obj using the splicer syntax <code>obj.[: r_dm :]</code>, and then combine the hash value.</p>
<h3>Hashing Base Classes</h3>
<p>Now, we can compute the combined hash value for all non-static data members. However, there's an important aspect we must address: the base classes or structs also need to be included in the hash calculation. Thanks to reflection, this task is less daunting than it might appear.</p>
<p>For a given type <code>T</code>, we can query its base classes using the meta-function <code>std::meta::bases_of(^^T, ctx)</code>. After defining a static array of these reflected base types, we can iterate over them. To obtain the reflected value of a type, we apply <code>std::meta::type_of(r_base)</code>. The entire snippet appears as follows:</p>
<pre><code class="language-cpp">static constexpr auto r_base_types = std::define_static_array(std::meta::bases_of(^^T, ctx));
</code></pre>
<pre><code class="language-cpp">static constexpr auto r_base_types = 
  std::define_static_array(std::meta::bases_of(^^T, ctx));

template for (constexpr auto r_base : r_base_types)
{
  using Base = typename[:std::meta::type_of(r_base):];
  static_assert(Hashing::Hashable&lt;Base&gt;, "Base class must be hashable");
  Utility::hash_combine(seed, static_cast&lt;const Base &amp;&gt;(obj));
}
</code></pre>
<p>Once again we can use splicer to define an alias for a base type. When we want to obtain a type from refected value of type we need to use <code>typename</code> before.</p>
<p>Now the whole template function looks like this:</p>
<pre><code class="language-cpp">template &lt;typename T&gt;
    requires std::is_class_v&lt;T&gt;
size_t calculate_hash(const T &amp;obj, size_t seed = 0)
{
    constexpr auto ctx = std::meta::access_context::unchecked();

    // combine hashes of base classes
    static constexpr auto r_base_types = std::define_static_array(std::meta::bases_of(^^T, ctx));

    template for (constexpr auto r_base : r_base_types)
    {
        using Base = typename[:std::meta::type_of(r_base):];
        static_assert(Hashing::Hashable&lt;Base&gt;, "Base class must be hashable");
        Utility::hash_combine(seed, static_cast&lt;const Base &amp;&gt;(obj));
    }

    // combine hashes of non-static data members
    static constexpr auto r_data_members = std::define_static_array(std::meta::nonstatic_data_members_of(^^T, ctx));

    template for (constexpr auto r_dm : r_data_members)
    {
        const auto&amp; member_value = obj.[:r_dm:];
        Utility::hash_combine(seed, member_value);
    }

    return seed;
}
</code></pre>
<h2>Enabling Hashing for Custom Types</h2>
<p>Now it is almost complete. The final consideration is how to enable specialization of <code>std::hash</code> for a custom type. One approach is to optin to our hashing implementation by defining an alias, <code>enabled_for_hashing</code>, within a custom class. This alias activates the generation of a hash value using our implementation.</p>
<pre><code class="language-cpp">struct EnabledForHashing_t {};

template &lt;typename T&gt;
concept EnabledForHashing = requires {
   typename T::enabled_for_hashing;
};

// specialization for types opted-in for hashing
template &lt;EnabledForHashing T&gt;
struct std::hash&lt;T&gt;
{
    size_t operator()(const T&amp; obj) const
    {
        return calculate_hash(obj);
    }
};

class Person
{
    using enabled_for_hashing = EnabledForHashing_t;

    //...
};
</code></pre>
<h2><strong>Conclusion</strong></h2>
<p>C++26 reflection lets us replace the boilerplate and fragility of a <code>tied()</code> - based hashing approach with a cleaner, more robust solution. By using the reflection operator (<code>^^</code>) to inspect an object's structure at compile time, we can generate consistent, maintainable hash implementations without requiring every type to expose a <code>std::tuple</code> of members or to opt in via tags or template variables. The result is less repetitive code, fewer user-facing APIs to learn, and a lower risk of forgetting a member when you extend a type.</p>
<p>That said, adoption comes with practical considerations. Reflection-based hashing improves ergonomics and enables more work at compile time (including <code>constexpr</code> hashing) but relies on compiler support and the current state of language/library tooling. For projects that must support older standards or compilers, keeping a fallback (the <code>tied()</code> pattern or explicit <code>std::hash</code> specializations) is reasonable. For new code targeting modern toolchains (e.g., GCC 16 and other compilers that implement C++26 reflection), the reflection approach is worth adopting.</p>
<p>Going forward, use reflection-based hashing to reduce maintenance burden, but: test hash behaviour for stability across builds if you depend on hash persistence, document any members you intentionally skip from hashing, and pay attention to compiler bugs and implementation details as toolchains mature. Overall, C++26 reflection is a strong step toward safer, more concise metaprogramming—and hashing is a clear, practical win.</p>
<p>As a next step, we can extend our implementation with features like attributes that will allow us to discard some values for calculating the hash, but I think it is a nice subject for a next post.</p>
<p>The example of code can be found at: <a href="https://godbolt.org/z/1covqd36r">https://godbolt.org/z/1covqd36r</a></p>
]]></content:encoded></item><item><title><![CDATA[How to Hash Objects Without Repetition: std::hash can be DRY]]></title><description><![CDATA[In my previous article on std::tie, I discussed how to simplify comparison operators for your classes using the std::tie() function.
Now we’ll explore a more complex case. Let's create a generic imple]]></description><link>https://blog.infotraining.pl/how-to-hash-objects-without-repetition</link><guid isPermaLink="true">https://blog.infotraining.pl/how-to-hash-objects-without-repetition</guid><category><![CDATA[C++]]></category><category><![CDATA[hashing functions]]></category><category><![CDATA[tuples]]></category><category><![CDATA[Concepts]]></category><dc:creator><![CDATA[Krystian Piękoś]]></dc:creator><pubDate>Fri, 06 Dec 2024 12:56:48 GMT</pubDate><content:encoded><![CDATA[<p>In my previous <a href="https://blog.infotraining.pl/comparisons-with-ties">article on <code>std::tie</code></a>, I discussed how to simplify comparison operators for your classes using the <code>std::tie()</code> function.</p>
<p>Now we’ll explore a more complex case. Let's create a generic implementation of a hashing function for a class or structure we develop.</p>
<h1>Repetitive hashing</h1>
<p>To provide a hash value for our custom object, we should write a <code>std::hash</code> specialization for our type.</p>
<p>A typical code example looks like this:</p>
<pre><code class="language-cpp">struct Person
{
    std::string first_name;
    std::string last_name;
    std::uint8_t age;
};

template &lt;&gt;
struct std::hash&lt;Person&gt; 
{
    size_t operator()(const Person&amp; p) const
    {
        std::size_t seed{};
        some_lib::hash_combine(seed, p.first_name);
        some_lib::hash_combine(seed, p.last_name);
        some_lib::hash_combine(seed, p.age);
        return seed;    
    }
};
</code></pre>
<p>To calculate the hash value, we need to call a function that incrementally combines the hash values of all our data members. This function often comes from an external library, like Boost - <code>boost::hash_combine()</code>. This task is repetitive and tedious. Let's find a better solution.</p>
<h1>Fold expressions &amp; hash_combine</h1>
<p>We can start by writing a variadic version of <code>hash_combine()</code>. Such a function should accept any number of arguments and return combined hash for all of them. This can be done using a fold expression with the “famous” <code>,</code> operator:</p>
<pre><code class="language-cpp">template &lt;typename... TValues&gt;
auto combined_hash(const TValues&amp;... values)
{
    size_t seed{};
    (..., some_lib::hash_combine(seed, values));

    return seed;
}
</code></pre>
<p>Now we can simplify hashing with the following code:</p>
<pre><code class="language-cpp">template &lt;&gt;
struct std::hash&lt;Person&gt; 
{
    size_t operator()(const Person&amp; p) const
    {
        return combined_hash(p.first_name, p.last_name, p.age);
    }
};
</code></pre>
<p>It's now possible to pass data members as arguments to our function.</p>
<h1>Hashing &amp; <code>std::tie</code></h1>
<p>As you might recall from my <a href="https://blog.infotraining.pl/comparisons-with-ties">previous post</a>, <code>std::tie()</code> can create a view that refers to the data members of our class. We used this to simplify the implementation of comparison operators:</p>
<pre><code class="language-cpp">struct Person
{
    std::string first_name;
    std::string last_name;
    std::uint8_t age;

    auto tied() const
    {
        return std::tie(first_name, last_name, age);
    }

    bool operator==(const Person&amp; other) const
    {
        return tied() == other.tied();
    }
};
</code></pre>
<p>Can we use the same trick in our hash implementation?</p>
<p>We can try to tie the data members together and combine their hash values like this:</p>
<pre><code class="language-cpp">template &lt;&gt;
struct std::hash&lt;Person&gt; 
{
    size_t operator()(const Person&amp; p) const
    {
        return combined_hash(p.tied());
    }
};
</code></pre>
<p>Now, we are passing a tuple as an argument to our <code>combined_hash()</code> function. Unfortunately, <code>std::tuple</code> does not have a <code>std::hash&lt;&gt;</code> specialization, so the compiler will generate an error. However, if we use <code>boost::hash_combine()</code>, it works because Boost can hash the tuples for us.</p>
<h1>std::apply to the rescue</h1>
<p>Let's pause for a moment and reexamine the code.</p>
<p>Now, we can calculate a combined hash for any set of hashable values:</p>
<pre><code class="language-cpp">std::string first_name = "John";
std::string last_name = "Smith";
uint8_t age = 42;
size_t hashed_value = combined_hash(first_name, last_name, age);
</code></pre>
<p>We need to call this function for arguments that are tied in a tuple. The code should look like this:</p>
<pre><code class="language-cpp">std::tuple tpl{"John"s, "Smith"s, 42};

size_t hashed_value = combined_hash(std::get&lt;0&gt;(tpl), std::get&lt;1&gt;(tpl), std::get&lt;2&gt;(tpl));
</code></pre>
<p>It occurs that in the standard library we have <code>std::apply(F&amp;&amp; f, Tuple&amp;&amp; t)</code> function that allows calling any function <code>f</code> with elements of <code>t</code> passed as arguments.</p>
<pre><code class="language-cpp">void foobar(const std::string&amp; foo, int bar) 
{
    std::cout &lt;&lt; "foobar(" &lt;&lt; foo &lt;&lt; ", " &lt;&lt; bar &lt;&lt; ")\n";
}

std::tuple args{"Foo"s, 42};
std::apply(foobar, args); // tha same as foobar(std::get&lt;0&gt;(args), std::get&lt;1&gt;(args))
</code></pre>
<p>Let’s apply <code>std::apply</code> to our needs :)</p>
<pre><code class="language-cpp">template &lt;&gt;
struct std::hash&lt;Person&gt; 
{
    size_t operator()(const Person&amp; p) const
    {
        return std::apply(combined_hash, p.tied());
    }
};
</code></pre>
<p>Unfortunately, this is not enough. The compiler will complain that it cannot deduce the template parameter <code>F</code> for <code>std::apply(F&amp;&amp; f, Tuple&amp;&amp; t)</code>. This is because our <code>combined_hash()</code> is a template function. We can easily solve this problem by wrapping the function in a closure (also known as a lambda). The working code will be as follows:</p>
<pre><code class="language-cpp">template &lt;&gt;
struct std::hash&lt;Person&gt; 
{
    size_t operator()(const Person&amp; p) const
    {
        auto hasher = [](const auto&amp;... args) { return combined_hash(args...); };
        return std::apply(hasher, p.tied());
    }
};
</code></pre>
<h1>Generic hashing solution for future classes</h1>
<p>This solution might seem complex, but it offers us a benefit. We can create a generic specialization for any custom type that has a <code>tied()</code> member function. We can provide a <code>std::hash&lt;T&gt;</code> specialization for each type that chooses to use our hashing implementation.</p>
<pre><code class="language-cpp">struct HashableForTiedMembers
{ };

template &lt;typename T&gt;
constexpr bool hashing_for_tied_members = false; 

template &lt;typename T&gt;
concept HashableForTied = std::derived_from&lt;T, HashableForTiedMembers&gt; || hashing_for_tied_members&lt;T&gt;;

template &lt;HashableForTied T&gt;
struct std::hash&lt;T&gt;
{
    size_t operator()(const T&amp; value) const
    {
        auto hasher = [](const auto&amp;... args) {
            return combined_hash(args...);
        };
        return std::apply(hasher, value.tied());
    }
};
</code></pre>
<p>The creator of the class can choose from two opt-in options:</p>
<ul>
<li><p>A class can inherit from the <code>HashableForTiedMembers</code> struct, which serves as a tag, enabling the compiler to specialize <code>std::hash</code> for that type. However, this option requires our class or struct to have constructors. For aggregates that derive from empty-base classes, additional <code>{}</code> are needed in the argument list, changing the syntax of object initialization.</p>
<pre><code class="language-cpp">struct HashableForTiedMembers
{ };

struct Person : HashableForTiedMembers
{
    std::string first_name;
    std::string last_name;
    std::uint8_t age;

    Person(std::string first_name, std::string last_name, std::uint8_t age)
        : first_name{std::move(first_name)}
        , last_name{std::move(last_name)}
        , age{age}
    {
    }

    auto tied() const
    {
        return std::tie(first_name, last_name, age);
    }
};
</code></pre>
</li>
<li><p>Alternatively, we can define the opt-in tag externally using template variables.</p>
<pre><code class="language-cpp">struct AggregatePerson
{
    std::string first_name;
    std::string last_name;
    std::uint8_t age;

    auto tied() const
    {
        return std::tie(first_name, last_name, age);
    }
};

// opt-in hashing for AggregatePerson
template &lt;&gt;
constexpr bool hashing_for_tied_members&lt;AggregatePerson&gt; = true;
</code></pre>
</li>
</ul>
<p>By using the <code>tied()</code> member function for hashing, we can easily implement <code>operator==</code>, which is needed for storing objects as keys in unordered associative containers. The following class can be used as a key in an <code>unordered_map</code>:</p>
<pre><code class="language-cpp">class Coordinate : public HashableForTiedMembers
{
public:
    Coordinate(int x, int y) noexcept
        : x_{x}
        , y_{y}
    { }

    auto tied() const noexcept
    {
        return std::tie(x_, y_);
    }

    bool operator==(const Coordinate&amp; other) const noexcept
    {
        return tied() == other.tied();
    }

private:
    int x_;
    int y_;
};

int main()
{
    std::unordered_map&lt;Coordinate, std::string&gt; coordinates;
	coordinates.emplace(Coordinate{0, 0}, "origin");
	coordinates.emplace(Coordinate{1, 0}, "right");
	coordinates.emplace(Coordinate{0, 1}, "up");
	coordinates.emplace(Coordinate{1, 1}, "up-right");
}
</code></pre>
<h1>Summary</h1>
<p>In this blog post, I showed a way to implement hashing for types in a generic way.</p>
<p>I hope you find it interesting. Let me know if you do.</p>
<p>The sample code can be found at <a href="https://godbolt.org/z/7767MP1zK">https://godbolt.org/z/7767MP1zK</a>.</p>
]]></content:encoded></item><item><title><![CDATA[How to Simplify Object Comparisons with Ties in C++11/14]]></title><description><![CDATA[When we want to check the equality of objects or compare them, we need to provide appropriate comparison operators for our class. The implementation usually needs to delegate comparisons to the corresponding member variables.
The typical code might l...]]></description><link>https://blog.infotraining.pl/comparisons-with-ties</link><guid isPermaLink="true">https://blog.infotraining.pl/comparisons-with-ties</guid><category><![CDATA[C++]]></category><category><![CDATA[tuples]]></category><category><![CDATA[comparison]]></category><dc:creator><![CDATA[Krystian Piękoś]]></dc:creator><pubDate>Thu, 07 Nov 2024 13:11:50 GMT</pubDate><content:encoded><![CDATA[<p>When we want to check the equality of objects or compare them, we need to provide appropriate comparison operators for our class. The implementation usually needs to delegate comparisons to the corresponding member variables.</p>
<p>The typical code might look like this:</p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;string&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;cassert&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;cstdint&gt;</span></span>

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Person</span>
{</span>
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> first_name;
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> last_name;
    <span class="hljs-built_in">std</span>::<span class="hljs-keyword">uint8_t</span> age;

    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span>==(<span class="hljs-keyword">const</span> Person&amp; other) <span class="hljs-keyword">const</span>
    {
        <span class="hljs-keyword">return</span> first_name == other.first_name 
               &amp;&amp; last_name == other.last_name
               &amp;&amp; age == other.age;
    }

    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span>&lt;(<span class="hljs-keyword">const</span> Person&amp; other) <span class="hljs-keyword">const</span>
    {
        <span class="hljs-keyword">if</span> (first_name == other.first_name)
        {
            <span class="hljs-keyword">if</span> (last_name == other.last_name)
            {
                <span class="hljs-keyword">return</span> age &lt; other.age;
            }

            <span class="hljs-keyword">return</span> last_name &lt; other.last_name;
        }

        <span class="hljs-keyword">return</span> first_name &lt; other.last_name;
    }
};

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span>
</span>{
    Person p1{<span class="hljs-string">"John"</span>, <span class="hljs-string">"Doe"</span>, <span class="hljs-number">33</span>};
    Person p2{<span class="hljs-string">"John"</span>, <span class="hljs-string">"Doe"</span>, <span class="hljs-number">33</span>};
    Person p3{<span class="hljs-string">"John"</span>, <span class="hljs-string">"Don"</span>, <span class="hljs-number">44</span>};

    assert(p1 == p2);
    assert(p2 &lt; p3);
}
</code></pre>
<p>We can simplify such a mundane task (especially for <code>operator &lt;</code>) when we use <code>std::tie()</code> function from C++11. The code can be rewritten to:</p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;string&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;tuple&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;cassert&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;cstdint&gt;</span></span>

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Person</span>
{</span>
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> first_name;
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> last_name;
    <span class="hljs-built_in">std</span>::<span class="hljs-keyword">uint8_t</span> age;

    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span>==(<span class="hljs-keyword">const</span> Person&amp; other) <span class="hljs-keyword">const</span>
    {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">std</span>::tie(first_name, last_name, age) == <span class="hljs-built_in">std</span>::tie(other.first_name, other.last_name, other.age);
    }

    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span>&lt;(<span class="hljs-keyword">const</span> Person&amp; other) <span class="hljs-keyword">const</span>
    {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">std</span>::tie(first_name, last_name, age) &lt; <span class="hljs-built_in">std</span>::tie(other.first_name, other.last_name, other.age);
    }
};
</code></pre>
<p>In C++14, we can make things even simpler by reducing repetition.</p>
<pre><code class="lang-cpp"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Person</span>
{</span>
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> first_name;
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> last_name;
    <span class="hljs-keyword">uint8_t</span> age;

    <span class="hljs-function"><span class="hljs-keyword">auto</span> <span class="hljs-title">tied</span><span class="hljs-params">()</span> <span class="hljs-keyword">const</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">std</span>::tie(first_name, last_name, age);
    }

    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span>==(<span class="hljs-keyword">const</span> Person&amp; other) <span class="hljs-keyword">const</span>
    {
        <span class="hljs-keyword">return</span> tied() == other.tied();
    }

    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span>&lt;(<span class="hljs-keyword">const</span> Person&amp; other) <span class="hljs-keyword">const</span>
    {
        <span class="hljs-keyword">return</span> tied() &lt; other.tied();
    }
};
</code></pre>
<h1 id="heading-how-it-works">How it works?</h1>
<h2 id="heading-tuples-with-references">Tuples with references</h2>
<p>Let's start with what are known as reference tuples. These are tuples that hold references to specific values.</p>
<pre><code class="lang-cpp"><span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> name = <span class="hljs-string">"John"</span>;
<span class="hljs-built_in">std</span>::<span class="hljs-keyword">uint8_t</span> age = <span class="hljs-number">42</span>;

<span class="hljs-function"><span class="hljs-built_in">std</span>::tuple&lt;<span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span>&amp;, <span class="hljs-built_in">std</span>::<span class="hljs-keyword">uint8_t</span>&amp;&gt; <span class="hljs-title">ref_tpl</span><span class="hljs-params">(name, age)</span></span>;
</code></pre>
<p>The created tuple called <code>ref_tpl</code> holds references to two variables: <code>name</code> and <code>age</code>. We can access them using the <code>std::get&lt;Index&gt;()</code> function:</p>
<pre><code class="lang-cpp"><span class="hljs-built_in">std</span>::<span class="hljs-built_in">cout</span> &lt;&lt; <span class="hljs-built_in">std</span>::get&lt;<span class="hljs-number">0</span>&gt;(ref_tpl) &lt;&lt; <span class="hljs-string">"\n"</span>; <span class="hljs-comment">// prints: "John"</span>

<span class="hljs-built_in">std</span>::get&lt;<span class="hljs-number">1</span>&gt;(ref_tpl) = <span class="hljs-number">33</span>; <span class="hljs-comment">// assigns 33 to age variable</span>
</code></pre>
<p>The tuple named <code>ref_tpl</code> holds references to two variables: <code>name</code> and <code>age</code>. We can access these variables using the <code>std::get&lt;Index&gt;()</code> function.</p>
<pre><code class="lang-cpp">ref_tpl = <span class="hljs-built_in">std</span>::make_tuple(<span class="hljs-string">"Adam"</span>, <span class="hljs-number">24</span>);
assert(name == <span class="hljs-string">"Adam"</span>);
assert(age == <span class="hljs-number">33</span>);
</code></pre>
<h2 id="heading-stdtie">std::tie</h2>
<p>Now <code>std::tie()</code> comes in handy. We can easily create reference tuples like this:</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">auto</span> ref_tpl = <span class="hljs-built_in">std</span>::tie(name, age); <span class="hljs-comment">// returns: std::tuple&lt;std::string&amp;, std::uint8_t&amp;&gt;</span>
</code></pre>
<p>This function determines the types of the arguments and returns a tuple that holds references to the passed lvalue arguments.</p>
<p>So the <code>tied()</code> method in our struct returns tuple that hold references to object’s members:</p>
<pre><code class="lang-cpp"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Person</span>
{</span>
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> first_name;
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> last_name;
    <span class="hljs-keyword">uint8_t</span> age;

    <span class="hljs-function"><span class="hljs-keyword">auto</span> <span class="hljs-title">tied</span><span class="hljs-params">()</span> <span class="hljs-keyword">const</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">std</span>::tie(first_name, last_name, age);
    }
};
</code></pre>
<h2 id="heading-comparing-tuples">Comparing tuples</h2>
<p>Tuples can be compared in lexicographic order. They perform comparisons based on their corresponding members.</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-built_in">std</span>::tuple&lt;<span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span>, <span class="hljs-built_in">std</span>::<span class="hljs-keyword">uint8_t</span>&gt; <span class="hljs-title">tpl1</span><span class="hljs-params">(<span class="hljs-string">"John"</span>, <span class="hljs-number">33</span>)</span></span>;
<span class="hljs-function"><span class="hljs-built_in">std</span>::tuple&lt;<span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span>, <span class="hljs-built_in">std</span>::<span class="hljs-keyword">uint8_t</span>&gt; <span class="hljs-title">tpl2</span><span class="hljs-params">(<span class="hljs-string">"John"</span>, <span class="hljs-number">33</span>)</span></span>;

assert(tpl1 == tpl2);
assert(tpl1 == <span class="hljs-built_in">std</span>::make_tuple(<span class="hljs-string">"John"</span>, <span class="hljs-number">33</span>));
assert(tpl1 &lt; <span class="hljs-built_in">std</span>::make_tuple(<span class="hljs-string">"John"</span>, <span class="hljs-number">44</span>));
</code></pre>
<p>You can also compare reference tuples:</p>
<pre><code class="lang-cpp"><span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> name = <span class="hljs-string">"John"</span>;
<span class="hljs-built_in">std</span>::<span class="hljs-keyword">uint8_t</span> age = <span class="hljs-number">42</span>;

assert((<span class="hljs-built_in">std</span>::tie(name, age) == <span class="hljs-built_in">std</span>::tuple&lt;<span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span>, <span class="hljs-built_in">std</span>::<span class="hljs-keyword">uint8_t</span>&gt;(<span class="hljs-string">"John"</span>, <span class="hljs-number">42</span>)));
assert(<span class="hljs-built_in">std</span>::tie(name, age) &lt; <span class="hljs-built_in">std</span>::make_tuple(<span class="hljs-string">"John"</span>, <span class="hljs-number">45</span>));
</code></pre>
<h2 id="heading-putting-it-all-together">Putting It All Together</h2>
<p>Now our simplified code should be easy to understand.</p>
<pre><code class="lang-cpp"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Person</span>
{</span>
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> first_name;
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> last_name;
    <span class="hljs-built_in">std</span>::<span class="hljs-keyword">uint8_t</span> age;

    <span class="hljs-function"><span class="hljs-keyword">auto</span> <span class="hljs-title">tied</span><span class="hljs-params">()</span> <span class="hljs-keyword">const</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">std</span>::tie(first_name, last_name, age);
    }

    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span>==(<span class="hljs-keyword">const</span> Person&amp; other) <span class="hljs-keyword">const</span>
    {
        <span class="hljs-keyword">return</span> tied() == other.tied();
    }

    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span>&lt;(<span class="hljs-keyword">const</span> Person&amp; other) <span class="hljs-keyword">const</span>
    {
        <span class="hljs-keyword">return</span> tied() &lt; other.tied();
    }
};
</code></pre>
<p>The <code>tied()</code> member function returns a tuple with references to the object's members in a specific order. This tuple works like a read-only view of the object's members and can be compared with another tuple, letting us write any comparison operator in a single line.</p>
]]></content:encoded></item></channel></rss>