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

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

 Respuesta aceptada

Matt J
Matt J el 2 de Abr. de 2024
Editada: Matt J el 2 de Abr. de 2024
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 comentarios

but I find that the newer, modular indexing can have surprising behavior
Nevertheless, the attached implementation seems to be doing alright. Note that I've implemented it with dictionaries, instead of containers.Map. You've declined to indicate your Matlab version, but if it is new enough to support dictionaries, that is recommended over containers.Map, although it should be easy enough to adapt to the latter.
A = testClass(["test1", "test2"], ["abc", "def"]);
A.test1
ans = "abc"
A.print_value('test1')
abc
Interesting - let me give this a shot.
I have R2022a so no dictionary but shouldn't be hard to change it to containers.Map and if it works, to dictionaries.
I was starting to take the approach of storing the functions that needs to have the subsref as a separate function and storing the function handle to the class object. It seems to work but isn't the cleaniest way to do it.
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:
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:

Iniciar sesión para comentar.

Más respuestas (1)

Matt J
Matt J el 2 de Abr. de 2024
Editada: Matt J el 2 de Abr. de 2024
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 comentarios

It seems to me that Matlab should only disable overload in the methods where it'll introduce recursion (subsref, subsagn) instead of in all class methods (including Static methods).
Yes - I looked into Dependent properties but not sure how to get it to work in this use case:
classdef testClass < handle
properties (Access = private)
objects
end
methods
function obj = testClass(keys, values)
obj.objects = containers.Map(keys, values);
end
function varargout = subsref(obj, S)
% Overload the "." operator for get access
switch S(1).type
case '.'
if obj.objects.isKey(S(1).subs)
[varargout{1}] = obj.objects(S(1).subs);
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_value(obj, key)
value = obj.(key); % Should call subsref?
disp(value);
end
end
end
and run with
A = testClass({'test1', 'test2'}, {'abc', 'def'})
A.test1
A.print_value('test1')
The idea is that testClass will be an Abstract class and its subclasses will add certain objects to its containers.Map(). To make it easier to manipulate, I want to be able to do something like
testClass1.object1.value = 500;
But I don't know the keys that the subclasses will have, is there a way to use Dependent to get this use case to work?
@Matt J No, subsref is only called by indexing operations invoked outside the classdef.
There is one caveat to that statement that may be useful. If you explicitly call subsref inside a method, it will call the subsref method. [If you're going to the effort to explicitly call the method, we'll trust you know what you're doing.] Consider the following class. Calling sample1 would not call the subsref method since it calls it implicitly; calling sample2 would because of the explicit subsref() call.
And yes, sample1 and sample2 don't return the same thing; note that subsref() operates on obj.x so sample1 returns the object while sample2 returns a double from the x property.
classdef sampleclass2101526
properties
x = 10:-1:1;
end
methods
function y = subsref(obj, substructArray)
fprintf("Inside subsref\n");
y = builtin('subsref', obj.x, substructArray);
end
function y = sample1(obj)
fprintf("Inside sample1\n");
y = obj(1);
end
function y = sample2(obj)
fprintf("Inside sample2\n");
y = subsref(obj, substruct('()', {1}));
end
end
end
@Cole It seems to me that Matlab should only disable overload in the methods where it'll introduce recursion (subsref, subsagn) instead of in all class methods (including Static methods).
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?
> 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...
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

Iniciar sesión para comentar.

Categorías

Más información sobre Customize Object Indexing en Centro de ayuda y File Exchange.

Etiquetas

Preguntada:

el 1 de Abr. de 2024

Comentada:

el 4 de Abr. de 2024

Community Treasure Hunt

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

Start Hunting!

Translated by