逃离 "inout hell"

Escaping from "inout hell"

我有一些非常简单的代码拒绝编译:

struct Wrapper(T)
{
    T t;

    bool opEquals(inout(Wrapper) other) inout
    {
        return t == other.t;
    }

    bool opEquals(inout(T) val) inout
    {
        return t == val;
    }
}

struct Test
{
    bool opEquals(Test t)
    {
        return true;
    }
}

void main()
{
    Wrapper!Test a, b;

    assert(a == b);

    //Error: inout method Test.opEquals is not 
    //callable using a mutable object
    assert(a == Test());
}

现在,我知道了问题所在,即 Test 没有定义 inout opEquals。然而,在 Test 中定义 opEquals 的另一个可变版本并不能解决这个问题,因为编译器只是忽略它并调用 inout 版本。有什么方法可以解决这个问题,而无需为可变的 constimmutable 定义 opEquals 重载?

只需删除 inout。编译器会自动为模板推断 const 等属性。

无论如何,您并未将 inout 用于其预期目的; inout 用于根据参数将可变、常量或不可变转换为函数的 return 类型。函数体必须假设最坏的情况(const),所以它不能调用非常量方法。

请注意,由于在您的示例中 Test.opEquals 不是 const,因此它只能在可变对象上调用。另请注意,在 D 中,const 是可传递的,因此 const(Wrapper!Test)const(Wrapper!(const(Test))) 相同。所以无论如何,你不能在 const/immutable 对象上调用 Test.opEquals (即使在包装器中),除非你使它成为 const.

所有 inout 都是为了使 return 类型的常量可以匹配函数参数的常量。如果你有

const(Foo) bar(const(Foo) f) {
{
    //...
    return f;
}

然后你将一个 mutableimmutable 对象传递给 bar,你最终得到一个 const 对象 returned,而如果你使用inout

inout(Foo) bar(inout(Foo) f) {
{
    //...
    return f;
}

return 类型与传递给 f 的参数具有相同的常量性。无论哪种方式,在函数内,f 都被有效地视为 const。您只能在其上调用 constinout 函数。因此,使 opEquals inout 毫无意义,因为它没有 return 任何参数。和制作const.

一样

这里的根本问题是您试图在 const 对象上调用可变函数。这是不合法的,因为它违反了 const。您有两个选项之一:

  1. 使 WrapperopEquals 可变。然后它可以在 TopEquals 可变时调用 opEquals

  2. 根据T如何定义opEquals,使用static if定义opEquals不同。

没有办法将 opEquals 的常量从 T 转发到 Wrapper,而不用 static if 显式地进行。例如

struct Wrapper(T)
{
    T t;

    static if(is(typeof({const T t; t == t;})))
    {
        bool opEquals(const Wrapper other) const
        {
            return t == other.t;
        }

        bool opEquals(const T val) const
        {
            return t == val;
        }
    }
    else
    {
        bool opEquals(Wrapper other)
        {
            return t == other.t;
        }

        bool opEquals(T val)
        {
            return t == val;
        }
    }
}

因为Wrapper是一个模板,所以purenothrow@safe会在其函数上进行推断,而[=17则没有属性推断=]、inoutimmutable.