2015-07-20 6 views
3

У меня есть очень простой код, который отказывается компилировать:Спасаясь от «Inout ад»

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 не определен inoutopEquals. Однако определение другой изменчивой версии opEquals в Test не устраняет эту проблему, поскольку компилятор просто игнорирует ее и вызывает версию inout независимо. Есть ли способ решить эту проблему, не прибегая к определению перегрузки opEquals для изменчивого, const и immutable?

+3

Ваш второй метод 'opEquals' в' Wrapper' указан неверно. Кроме того, поскольку 'opEquals' не изменяет свой аргумент, вы можете просто сделать его' const', и он будет работать как с изменяемыми, так и с неизменными типами. –

+0

Я считаю, что проблема заключается в том, что я отмечен 'Wrapper.opEquals' как' inout', а не потому, что аргумент помечен как 'inout'. Удаление 'inout' из метода заставляет его компилироваться, но тогда это не сработает, если я создам' immutable' или 'const'' Wrapper'. – Meta

+0

Он работает, если вы создаете 'opEquals' const, который вы должны делать так или иначе, потому что' opEquals' не должен ничего изменять. http://dpaste.dzfl.pl/6cab4a419488 –

ответ

5

Все inout предназначено для того, чтобы константа возвращаемого типа могла соответствовать константе аргумента функции. Если у вас есть

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

и вы передаете mutable или immutable объект bar, вы в конечном итоге с const возвращаемый объект, в то время как при использовании inout

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

возвращаемый тип имеет тот же константность как аргумент передан f. В любом случае, в пределах функции, f эффективно обрабатывается как const. Вы можете только называть const и inout функциями. Таким образом, создание opEqualsinout бессмысленно, потому что оно не возвращает никаких аргументов. Это то же самое, что и делать это const.

Ваша основная проблема заключается в том, что вы пытаетесь вызвать изменяемую функцию на объекте const. И это не законно, потому что это нарушает const. У вас есть один из двух вариантов:

  1. Марка Wrapper «ы opEquals изменяемых. Затем он может позвонить opEquals, когда T's opEquals изменен.

  2. Использование static if определить opEquals по-разному в зависимости от того, как T определяется 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 шаблон, pure, nothrow и @safe будет выведено на его функции, но нет никакого логического вывода атрибут const, inout или immutable.

+0

Мое замешательство проистекает из ошибочного предположения, что 'bool opEquals inout' должен действовать как изменяемый, когда' Wrapper' изменчиво. Я думаю, что сейчас у меня есть хорошее понимание, почему именно это не сработает ... Это действительно одна большая головная боль, если вы хотите, чтобы ваш тип работал правильно с любой «константой». – Meta

+0

@Meta Да, способ форвардной константы был бы полезен, но именно то, как это должно работать, становится интересным вопросом. Это обсуждалось ранее, но еще не ушло. К счастью, он действительно появляется только при обертке типов, и это происходит в основном с диапазонами, а диапазоны и 'const' взаимодействуют достаточно плохо, что отсутствие' const' для таких функций, как 'front' и' empty', не имеет особого значения. –

1

Просто удалите inout. Компилятор автоматически передает атрибуты, такие как const для шаблонов.

В любом случае вы не используете inout по своему прямому назначению; inout предназначен для передачи изменяемого, const или неизменяемого типа возвращаемого значения функции на основе его параметров. Тело функции должно принимать худшее (const), поэтому оно не может вызывать непостоянные методы.

Обратите внимание, что поскольку в вашем примере Test.opEquals не является const, его можно вызвать только на изменяемый объект. Также обратите внимание, что в D const транзитивен, поэтому const(Wrapper!Test) такой же, как const(Wrapper!(const(Test))). Поэтому, несмотря ни на что, вы не можете вызвать Test.opEquals на объект const/immutable (даже в обертке), если вы не сделаете его const.

+0

Я не могу удалить 'inout' из' opEquals', так как тогда он не будет работать с объектами 'immutable' или' const'. Давайте перейдем к 'const', чтобы лучше проиллюстрировать свою проблему. Глядя на [следующий код] (http://dpaste.dzfl.pl/a3242543f31e), вы понимаете, почему он не компилируется, и почему я не могу просто удалить 'inout' из' Wrapper.opEquals'? – Meta

+0

@Meta Невозможно скомпилировать, потому что 'Test.opEquals' не' const'. Это не сработает без обертки. 'assert (const (Test)() == const (Test)())' –

+0

Да, поэтому мы вернулись к моему первоначальному вопросу. Думаю, это означает, что ответ «нет». – Meta

Смежные вопросы