I would like to overload only one type of subsref calls (the '()' type) for a particular class and leave any other calls to Matlab's built in subsref -- specifically, I want Matlab to handle property/method access via the '.' type. But, it seems like Matlab's 'builtin' function doesn't work when subsref is overloaded in a class.
Consider this class:
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
To use the overloaded subsref method, I do this:
t = TestBuiltIn;
t.testprop
>> This is the overloaded method
That's as expected. But now I want to call Matlab's built in subsref method. To make sure I'm doing things right, first I try out a similar call on a struct:
x.testprop = 'Accessed correctly';
s.type = '.';
s.subs = 'testprop';
builtin('subsref', x, s)
>> Accessed correctly
That's as expected as well. But, when I try the same method on TestBuiltIn:
builtin('subsref', t, s)
>> This is the overloaded method
...Matlab calls the overloaded method rather than the built in method. Why does Matlab call the overloaded method when I requested that it call the builtin method?
UPDATE:
In response to @Andrew Janke's answer, that solution almost works but doesn't quite. Consider this class:
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
All of this works:
t = TestIndexing;
t(1)
>> overloaded method
t.prop1
>> 1
t.child
>> [TestIndexing instance]
t.child.prop1
>> 2
But this doesn't work; it uses the built in subsref for the child rather than the overloaded subsref:
t.child(1)
>> [TestIndexing instance]
Note that the above behavior is inconsistent with both of these behaviors (which are as expected):
tc = t.child;
tc(1)
>> overloaded method
x.child = t.child;
x.child(1)
>> overloaded method
It's possible, IIRC. To change ()
but not {}
and '.', write your subsref
method to pass those other cases along to the builtin subsref from within your overloaded subsref, instead of trying to explicitly call the builtin from outside.
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
(And if that builtin
call doesn't work, you can just put in cases that use .
and {}
directly, because the subsref
overload is ignored inside the class definition.)
To make it fully functional, you may need to change B to a varargout, and add chaining behavior in to the "()" case.
To expand on the explanation given on the Mathworks board, builtin only works from within an overloaded method to access the pre-existing (built in) method.
Overloading a method in Matlab effectively shadows the built in implementation from everything except the method doing the shadowing, and the method doing the shadowing must use builtin to access the built in implementation instead of recursing into itself.
In general. You should use builtin(m,s)
inside the function that is been overloaded. This is specified clearly in MATLAB documentation.
http://www.mathworks.com/help/matlab/ref/builtin.html
builtin(function,x1,...,xn) executes the built-in function with the
input arguments x1 through xn. Use builtin to execute the original
built-in from within a method that overloads the function. To work
properly, you must never overload builtin.
Consider this code:
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
and test command
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
In your updated version of this problem, when you call t.child(1)
, the subsref
function will receive argument s
with s(1).type='.', s(1).subs='child'
and s(2).type='()', s(2).subs='1'
. The evaluation of this expression is not in a step-by-step manner, as Andrew Janke mentioned in his answer. As a result, when overriding subsref
, you should handle this operation chain, by first processing the '.' operator. Here is a incomplete example for your case,
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
You can also find a detailed code sample and instruction at Code Patterns for subsref and subsasgn Methods.
One more thing you may need to know, is that method members in this class will also be invoked through subsref
with '.' operation. Look at this subject subsref on classes: how to dispatch methods?, you will find that the builtin
function has no return value (since the invoked method has no return value). However, the return value of builtin
is assigned to v
(even though v
is replaced by varargout
), which is an obvious error. The author also gives a temporary solution by using try ... catch
to resolve this error.