Search code examples
oopmatlaboverridingbuilt-in

Why can't I use builtin for classes that overload subsref?


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

Solution

  • 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.