Thread overview
opCmp with and without const
Dec 06, 2019
berni44
Dec 06, 2019
Paul Backus
Dec 06, 2019
Basile B.
Dec 06, 2019
Jonathan M Davis
December 06, 2019
In std.typecons, in Tuple there are two opCmp functions, that are almost identical; they only differ by one being const and the other not:

        int opCmp(R)(R rhs)
        if (areCompatibleTuples!(typeof(this), R, "<"))
        {
            static foreach (i; 0 .. Types.length)
            {
                if (field[i] != rhs.field[i])
                {
                    return field[i] < rhs.field[i] ? -1 : 1;
                }
            }
            return 0;
        }

        int opCmp(R)(R rhs) const
        if (areCompatibleTuples!(typeof(this), R, "<"))
        {
            static foreach (i; 0 .. Types.length)
            {
                if (field[i] != rhs.field[i])
                {
                    return field[i] < rhs.field[i] ? -1 : 1;
                }
            }
            return 0;
        }


What is the reason for having this? (I guess, that it's because the function may indirectly call opCmp of other types which may or may not be const.)

My real question is: Can this code duplication be avoided somehow? (I ask, because I've got a PR running, which increases the size of these functions and it doesn't feel good to have two long, almost identical functions.)
December 06, 2019
On Friday, 6 December 2019 at 07:03:45 UTC, berni44 wrote:
> My real question is: Can this code duplication be avoided somehow? (I ask, because I've got a PR running, which increases the size of these functions and it doesn't feel good to have two long, almost identical functions.)

You can use a template this parameter [1] to have a new copy of opCmp generated for each qualified version of Tuple it's called with:

int opCmp(R, this This)(R rhs) const
if (areCompatibleTuples!(typeof(this), R, "<"))
{
    ...
}

This may lead to binary bloat, though, since you can potentially have separate instantiations for mutable, const, immutable, inout, shared, etc.

[1] https://dlang.org/spec/template.html#template_this_parameter
December 06, 2019
On Friday, 6 December 2019 at 07:03:45 UTC, berni44 wrote:
> In std.typecons, in Tuple there are two opCmp functions, that are almost identical; they only differ by one being const and the other not:
>
>         int opCmp(R)(R rhs)
>         if (areCompatibleTuples!(typeof(this), R, "<"))
>         {
>             static foreach (i; 0 .. Types.length)
>             {
>                 if (field[i] != rhs.field[i])
>                 {
>                     return field[i] < rhs.field[i] ? -1 : 1;
>                 }
>             }
>             return 0;
>         }
>
>         int opCmp(R)(R rhs) const
>         if (areCompatibleTuples!(typeof(this), R, "<"))
>         {
>             static foreach (i; 0 .. Types.length)
>             {
>                 if (field[i] != rhs.field[i])
>                 {
>                     return field[i] < rhs.field[i] ? -1 : 1;
>                 }
>             }
>             return 0;
>         }
>
>
> What is the reason for having this? (I guess, that it's because the function may indirectly call opCmp of other types which may or may not be const.)
>
> My real question is: Can this code duplication be avoided somehow?

Usually `inout` is used. I'm pretty sure this is not possible here otherwise it would be done to avoid the dup.

> (I ask, because I've got a PR running, which increases the size of these functions and it doesn't feel good to have two long, almost identical functions.)

Well the content of body could be mixed in


December 06, 2019
On Friday, December 6, 2019 12:03:45 AM MST berni44 via Digitalmars-d-learn wrote:
> In std.typecons, in Tuple there are two opCmp functions, that are almost identical; they only differ by one being const and the other not:
>
>          int opCmp(R)(R rhs)
>          if (areCompatibleTuples!(typeof(this), R, "<"))
>          {
>              static foreach (i; 0 .. Types.length)
>              {
>                  if (field[i] != rhs.field[i])
>                  {
>                      return field[i] < rhs.field[i] ? -1 : 1;
>                  }
>              }
>              return 0;
>          }
>
>          int opCmp(R)(R rhs) const
>          if (areCompatibleTuples!(typeof(this), R, "<"))
>          {
>              static foreach (i; 0 .. Types.length)
>              {
>                  if (field[i] != rhs.field[i])
>                  {
>                      return field[i] < rhs.field[i] ? -1 : 1;
>                  }
>              }
>              return 0;
>          }
>
>
> What is the reason for having this? (I guess, that it's because the function may indirectly call opCmp of other types which may or may not be const.)
>
> My real question is: Can this code duplication be avoided somehow? (I ask, because I've got a PR running, which increases the size of these functions and it doesn't feel good to have two long, almost identical functions.)

The issue is that there's no guarantee that the types being wrapped have a const opCmp. So, you can't just slap const or inout on Tuple's opCmp and have it work, but you do want it to be const if it can be const. So, two overloads are declared, and the template constraint takes care of checking whether that particular overload can be instantiated.

A mixin could be used for the function bodies to avoid duplicating the internals, and it may be possible to use template this parameters as Paul Backus suggested (I'm not very familiar with template this parameters, so I don't know how well they'll work in this particular case), but ultimately, one way or another, you need to have a non-const opCmp declared for when the wrapped types don't have an opCmp that works with const and a const or inout opCmp for when the wrapped types do have an opCmp that works with const.

- Jonathan M Davis