Overloading subsrefs for class and using it from another method in the same class

67 views (last 30 days)
Take the following example:
classdef testClass < handle
properties (Access = private)
number
end
methods
function obj = testClass(value)
obj.number = value;
end
function varargout = subsref(obj, S)
% Overload the "." operator for get access
switch S(1).type
case '.'
if strcmp(S(1).subs, 'number')
[varargout{1}] = obj.number+1; % We always add 1 to number
return;
elseif any(strcmp(S(1).subs, methods(obj)))
% If the field name is a method of the object,
% call the method using feval
if numel(S) > 1 && strcmp(S(2).type, '()')
% If there are arguments for the method, pass them
[varargout{1:nargout}] = feval(S(1).subs, obj, S(2).subs{:});
else
% Otherwise, just call the method
[varargout{1:nargout}] = feval(S(1).subs, obj);
end
return;
end
end
error('Not handled');
end
function print_number(obj)
num = obj.number; % Should call subsref?
fprintf('%g\n', num);
end
end
end
And I run this:
A = testClass(3);
Invalid expression. Check for missing multiplication operator, missing or unbalanced delimiters, or other syntax error. To construct matrices, use brackets instead of parentheses.
A.number; % This returns 4, as expected.
However:
A.print_number() % This prints "3".
How come A.print_number() does not print 4?
How do I code my subsref so that it can work properly when calling from a method of the same class?
I would like to be able to do A.number within testClass methods and have it call the overloaded subsref without having to define set.number or get.number methods. In my real use case, testClass would contain a containers.Map(). The subsref would over loaded so that testClass.key1 would return the value of the containers.Map() with a key of "key1".

Accepted Answer

Matt J
Matt J on 2 Apr 2024 at 1:53
Edited: Matt J on 2 Apr 2024 at 1:58
As an alternative to subsref or set/get, you could look at RedefinesDot, but I find that the newer, modular indexing can have surprising behavior, discussed extensively at,
  4 Comments
Cole
Cole on 3 Apr 2024 at 19:58
Using RedefinesDot works for me. But your code only works R2022b or later. I tried in R2022a and it ran into a recursively loop but R2024a worked fine.
As a note to whoever is reading this:
dotReference/dotAssign is only called if you are accessing a variable that does not exist or is Access=private.
For example: if "test1" in the above example is a property of the class and it's not a private property, then A.test1 will not call dotReference, it will directly access "test1" property fro mthe class.
Other related questions on RedefinesDot:
Matt J
Matt J on 4 Apr 2024 at 1:11
Using RedefinesDot works for me. But your code only works R2022b or later.
That is strange. I implemented essentially the same thing in R2021b here with no difficulties:

Sign in to comment.

More Answers (1)

Matt J
Matt J on 2 Apr 2024 at 1:18
Edited: Matt J on 2 Apr 2024 at 1:47
No, subsref is only called by indexing operations invoked outside the classdef. This design decision was to avoid triggering recursions when indexing operations are done inside the subsref method itself. For example, you would not want this instance of obj.number to call subsref,
[varargout{1}] = obj.number+1;
as that would cause an infinite recursion. I'm not sure why such recursions couldn't have been blocked the same way they are blocked for set/get methods, but that's just the way it's always been.
I would like to be able to do A.number within testClass methods and have it call the overloaded subsref without having to define set.number or get.number methods.
I don't know why you think avoiding set and get methods is beneficial. Probably for different reasons, though, I would also not recommend set/get methods for number. I would instead recommend introducing a Dependent property:
classdef testClass < handle
properties (Access = private)
number
end
properties (Dependent)
Number
end
methods
function obj = testClass(value)
obj.number = value;
end
function val=get.Number(obj)
val=obj.number+1;
end
function print_number(obj)
num = obj.Number; % Should call subsref?
fprintf('%g\n', num);
end
end
end
  4 Comments
Cole
Cole on 2 Apr 2024 at 4:21
> Can you prove that calling the overloaded subs* methods won't ever introduce recursion in those other class methods? That there aren't any circumstances where the subs* methods would call back to the same methods that called them?
No, I can't prove this. I'm sure it can happen. I would argue that the recent mixin.indexing.Redefines causes just as much recursion if not handled properly, in the same way this will too.
Nevertheless, I see your point - but I think there should be flexibility in setting certain methods to allow for subsref/subsagn. Perhaps have it in the method (allowSubs) type of a block for an Abstract class.
Because what I'm getting stuck at is I'm trying to create an Abstract class that allows subclasses to overload a few specific methods. And in those methods, I can't see a case where there will be recursion would happen. Because the alternative is that I really can't define my Abstract class to be user-friendly, where subsref/subsagn would allow me to Abstract away a lot of the details of the implementation yet allowing the user to override certain methods. The best I can do right now is to have the class store function handles and ask the user to implement their function and set the function handles to their custom function. Which is not a great design scheme either...
Matt J
Matt J on 3 Apr 2024 at 10:40
Edited: Matt J on 3 Apr 2024 at 18:02
Can you prove that calling the overloaded subs* methods won't ever introduce recursion in those other class methods? That there aren't any circumstances where the subs* methods would call back to the same methods that called them?
Even if there is no way to make subsref recursion-proof, the same danger already exists with property set/get methods, as in the example below. So, there is already precedent for making the scope of overloaded indexing method-specific. According to my conversations with @James Lebak, this nonuniformity in the scope of overloaded indexing is something MathWorks is trying to move away from. I'm not sure what the drawbacks of such nonuniformity are seen to be.
classdef myclass
properties
prop
end
methods
function val=get.prop(obj)
val=subget(obj);
end
function val=subget(obj)
val=obj.prop;
end
end
end

Sign in to comment.

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!