Почему я не могу использовать встроенный для классов, которые перегружают subsref?

Я хотел бы перегрузить только один тип вызовов subsref (тип '()') для определенного класса и оставить любые другие вызовы для Matlab, встроенного в subsref - в частности, я хочу, чтобы Matlab обрабатывал доступ к свойствам/методам через '. ' тип. Но похоже, что встроенная функция Matlab не работает, когда subsref перегружен в классе.

Рассмотрим этот класс:

classdef TestBuiltIn
    properties
        testprop = 'This is the built in method';
    end

    methods
        function v = subsref(this, s)
            disp('This is the overloaded method');
        end
    end
end

Чтобы использовать перегруженный метод subsref, я делаю следующее:

t = TestBuiltIn;
t.testprop
    >> This is the overloaded method

Это как и ожидалось. Но теперь я хочу вызвать встроенный в Matlab метод subsref. Чтобы убедиться, что я все делаю правильно, сначала я пробую аналогичный вызов структуры:

x.testprop = 'Accessed correctly';
s.type = '.';
s.subs = 'testprop';
builtin('subsref', x, s)
    >> Accessed correctly

Это также ожидаемо. Но когда я пробую тот же метод в TestBuiltIn:

builtin('subsref', t, s)
    >> This is the overloaded method

... Matlab вызывает перегруженный метод, а не встроенный метод. Почему Matlab вызывает метод overloaded, когда я запросил, чтобы он вызывал встроенный метод?

ОБНОВЛЕНИЕ: в ответ на ответ @Andrew Janke это решение почти работает, но не совсем. Рассмотрим этот класс:

classdef TestIndexing
    properties
        prop1
        child
    end

    methods
        function this = TestIndexing(n)
            if nargin==0
                n = 1;
            end

            this.prop1 = n;
            if n<2
                this.child = TestIndexing(n+1);
            else
                this.child = ['child on instance ' num2str(n)];
            end
        end

        function v = subsref(this, s)
            if strcmp(s(1).type, '()')
                v = 'overloaded method';
            else
                v = builtin('subsref', this, s);
            end
        end
    end
end

Все это работает:

t = TestIndexing;
t(1)
    >> overloaded method
t.prop1
    >> 1
t.child
    >> [TestIndexing instance]
t.child.prop1
    >> 2

Но это не работает; он использует встроенный subsref для дочернего элемента, а не перегруженный subsref:

t.child(1)
    >> [TestIndexing instance]

Обратите внимание, что приведенное выше поведение несовместимо с обоими этими поведениями (которые, как и ожидалось):

tc = t.child;
tc(1)
    >> overloaded method

x.child = t.child;
x.child(1)
    >> overloaded method

person Ben    schedule 24.04.2013    source источник
comment
Не уверен, что я полностью понимаю, но я думаю, что сначала вызывается встроенная функция, а затем в этом вызове вызывается метод перегрузки. Если возможно, поставьте точку останова в полезном месте.   -  person Dennis Jaheruddin    schedule 24.04.2013
comment
Что-то совершенно другое: я надеюсь, что вы просто попытаетесь сделать это из личного интереса, так как для большинства проблем перегрузка subsref может быть не самым лучшим решением.   -  person Dennis Jaheruddin    schedule 24.04.2013
comment
Я не уверен, что вы подразумеваете под встроенной функцией, вызываемой первой - если бы это было гипотетически (не знаю, как, но скажем), я думаю, мне нужно было бы установить точку останова внутри встроенной функции subsref Matlab для проверки. Но это нативная функция, а не m-функция, поэтому я не могу поставить там точку останова.   -  person Ben    schedule 24.04.2013
comment
Что я действительно хочу сделать, так это изменить поведение индексации () моего класса, как это делает класс griddedInterpolant в Matlab. Но я не хочу переписывать {} и . ссылки на поведение с нуля. К сожалению, этот вопрос наводит меня на мысль, что невозможно изменить одно поведение, не реализовав с нуля другие.   -  person Ben    schedule 24.04.2013


Ответы (4)


Это возможно, IIRC. Чтобы изменить (), но не {} и '.', напишите свой метод subsref для передачи этих других случаев во встроенную подссылку из вашей перегруженной подссылки вместо того, чтобы пытаться явно вызывать встроенную извне.

function B = subsref(A, S)
    % Handle the first indexing on your obj itself
    switch S(1).type
        case '()'
            B = % ... do your custom "()" behavior ...
        otherwise
            % Enable normal "." and "{}" behavior
            B = builtin('subsref', A, S(1))
        end
    end
    % Handle "chaining" (not sure this part is fully correct; it is tricky)
    orig_B = B; % hold on to a copy for debugging purposes
    if numel(S) > 1
        B = subsref(B, S(2:end)); % regular call, not "builtin", to support overrides
    end
end

(И если этот вызов builtin не работает, вы можете просто указать случаи, которые используют . и {} напрямую, потому что перегрузка subsref игнорируется внутри определения класса.)

Чтобы сделать его полностью функциональным, вам может потребоваться изменить B на varargout и добавить поведение цепочки в случае "()".

person Andrew Janke    schedule 24.04.2013
comment
Спасибо, это почти работает, но не совсем -- смотрите обновление в моем вопросе. - person Ben; 25.04.2013
comment
Что происходит там, так это то, что в Matlab выражение с множественной индексацией, такой как foo.bar.baz(3), вместо того, чтобы оцениваться по шагам, например, делать foo.bar, а затем передавать результат в .baz, а затем передавать этот результат в (3), будет анализировать все выражение и передавать его в одном вызове subsref. (Это неинтуитивно.) Таким образом, ваш subsref должен просмотреть S, обработать S(1), а затем обработать поведение цепочки, вручную передав результаты вместе с S(2:end) другому вызову subsref, который затем выберет перегруженный метод. - person Andrew Janke; 25.04.2013
comment
Я изменил свой пример кода, чтобы показать поведение цепочки. Это немного сложно, и я не могу проверить это на правильность, потому что у меня сейчас нет Matlab (извините), но это в основном то, что вам нужно сделать. - person Andrew Janke; 25.04.2013
comment
(Обратите внимание, что важной частью является то, что связанный вызов subsref является обычным вызовом subsref(), а не через встроенную функцию. Выполнение всего этого с помощью одного встроенного вызова будет игнорировать (некоторые из) переопределенные методы для результатов промежуточного выражения, что выглядит так, как вы попал в ваши примеры обновлений.) - person Andrew Janke; 25.04.2013
comment
P.S. Потерпи; работать с subsref, subsasgn и их друзьями действительно сложно, и нужно много работать, чтобы добиться полной корректности. Поэкспериментируйте и тщательно перечитайте документацию. - person Andrew Janke; 25.04.2013

Чтобы расширить объяснение, данное в Доска Mathworks, встроенная функция работает только из перегруженного метода для доступа к уже существующему (встроенному) методу.

Перегрузка метода в Matlab эффективно затеняет встроенную реализацию от всего, кроме метода, выполняющего затенение, и метод, выполняющий затенение, должен использовать встроенную функцию для доступа к встроенной реализации вместо рекурсии в себя.

person Ben    schedule 24.04.2013

В основном. Вы должны использовать builtin(m,s) внутри перегруженной функции. Это четко указано в документации MATLAB.

http://www.mathworks.com/help/matlab/ref/builtin.html

встроенная(функция,x1,...,xn) выполняет встроенную функцию с входными аргументами от x1 до xn. Используйте встроенный для выполнения исходного встроенного из метода, который перегружает функцию. Для правильной работы вы никогда не должны перегружать встроенные функции.

Рассмотрим этот код:

classdef TestBuiltIn
    properties
        testprop = 'This is the built in method';
        testprop2 = 'This is the derived subsref ';
    end
    methods

        function v = subsref(m, s)
            disp('enter subsref no matter how!');
            v = builtin('subsref',m, s);
        end
    end
end

и тестовая команда

clear;
t = TestBuiltIn;
builtin('subsref', t, s)
s.type = '.';
s.subs = 'testprop';
s2 = s;
s2.subs = 'testprop2';

>> builtin('subsref', t, s1)

enter subsref no matter how!

ans =

This is the derived subsref 

>> builtin('subsref', t, s)
enter subsref no matter how!

ans =

This is the built in method
person Community    schedule 24.04.2013

В вашей обновленной версии этой задачи при вызове t.child(1) функция subsref получит аргумент s с s(1).type='.', s(1).subs='child' и s(2).type='()', s(2).subs='1'. Оценка этого выражения не является пошаговой, как упомянул Эндрю Янке в своем ответе. В результате при переопределении subsref вы должны обрабатывать эту цепочку операций, сначала обрабатывая '.' оператор. Вот неполный пример для вашего случая,

function v = subsref(this, s)
    switch s(1).type
       case '.'
           member = s(1).subs;
           if ismethod(this, member)
               % invoke builtin function to access method member
               % there is issue about the number of output arguments
               v = builtin('subsref',this,s);
           elseif isprop(this, member) % property
               if length(s) == 1
                   % invoke builtin function to access method member
                   v = builtin('subsref', this, s);
               elseif length(s) == 2 && strcmp(s(2).type,'()')
                   % this is where you evaluate 'tc.child(1)'
               else
                   % add other cases when you need, otherwise calling builtin
               end
           else
               % handling error.
           end
       case '()'
           % this is where you evaluate 't(1)'
           % you may need to handle something like 't(1).prop1', like the '.' case
       otherwise
           % by default, calling the builtin.
    end
end

Вы также можете найти подробный пример кода и инструкции по адресу Шаблоны кода для методов subsref и subsasgn.

Еще одна вещь, которую вам, возможно, необходимо знать, это то, что члены метода в этом классе также будут вызываться через subsref с '.' операция. Посмотрите на эту тему subsref по классам: как отправлять методы?, вы поймете обнаружить, что функция builtin не имеет возвращаемого значения (поскольку вызванный метод не имеет возвращаемого значения). Однако возвращаемое значение builtin присваивается v (хотя v заменено на varargout), что является очевидной ошибкой. Автор также предлагает временное решение, используя try ... catch для устранения этой ошибки.

person Gary Wang    schedule 03.08.2017