2

I would like to have a class which wraps up a containers.Map. In addition, I want to be able use () indexing to access the sub-map from the parent class, for example:

>> map = containers.Map('Foo', 'Bar');
>> ex = Example(map);
>> ex('Foo')

ans =

Bar

The code for this is below, and it works well. The only problem I am having is with other methods defined on the class. According to the docs, I understand I need to override numArgumentsFromSubscript (somehow?) to help nargout. My crude attempt at simply using numel(obj) as I've seen mentioned online works in most cases, but not when the function you are calling has no output arguments (in which case numel(obj) == 1 ~= 0).

Using the example code below,

>> ex.outGoodbye

ans =

Goodbye

Great! However,

>> ex.sayHello
Error using Example/sayHello
Too many output arguments.

Error in Example/subsref (line 17)
                 [varargout{1:nargout}] = builtin('subsref', obj, struct);

How can you fix this?

classdef Example
    % =====================================================================
    properties
        map
    end
    % =====================================================================
    methods
        % -----------------------------------------------------------------
        function obj = Example(map)
            obj.map = map;
        end
        % -----------------------------------------------------------------
        function varargout = subsref(obj, struct)
            if strcmp(struct(1).type, '()')
                 [varargout{1:nargout}] = builtin('subsref', obj.map, struct);
            else
                 [varargout{1:nargout}] = builtin('subsref', obj, struct);
            end
        end
        % -----------------------------------------------------------------
        function n = numArgumentsFromSubscript(obj, struct, indexingContext)
            n = numel(obj); % Necessary to overload subsref - for some reason
        end
        % -----------------------------------------------------------------
        function obj = subsasgn(obj, struct, varargin)
            if strcmp(struct(1).type, '()')
                obj = builtin('subsasgn', obj.map, struct, varargin{:});
                obj = Example(obj);
            else
                obj = builtin('subsasgn', obj, struct, varargin{:});
            end
        end
        % -----------------------------------------------------------------
        function sayHello(obj)
            disp('Hello'); % nargout == 0. Does NOT work
        end
        % -----------------------------------------------------------------
        function out = outGoodbye(obj)
            out = 'Goodbye'; % nargout > 0. Works
        end
        % -----------------------------------------------------------------
    end
    % =====================================================================
end
rwb
  • 4,309
  • 8
  • 36
  • 59
  • Why do you need to overload `numArgumentsFromSubscript` if you're just going to return `numel(obj)`? Typically you would have some branching logic in `numArgumentsFromSubscript` otherwise you'd just rely on the built-in handling. – Suever Apr 19 '16 at 11:23
  • Because it does seem to do _something_. Without overloading `numArgumentsFromSubscript` the following example doesn't work (apologies for horrible formatting): `strct.property = 'Prop'`, `map = containers.Map('Foo', strct)`, `ex = Example(map)`, `ex('Foo').property` `Error using Example/subsref (line 15) Insufficient number of outputs from right hand side of equal sign to satisfy assignment.` – rwb Apr 19 '16 at 11:32
  • Ok gotcha. I'll definitely look into this. I have done a lot of overloading of `subsref` and it's really difficult to work with in general. – Suever Apr 19 '16 at 11:42
  • Excellent. Thank you so much - I really appreciate the help. I agree it seems unnecessarily complicated. I guess this is what happens when object orientation is a bolted on as an after thought... – rwb Apr 19 '16 at 12:04
  • Let me know if the answer I have provided isn't clear enough. This was definitely a fun one to investigate! – Suever Apr 19 '16 at 14:00
  • Thank you so much for your help. My preference is for the `nargout` method, and modifying `numArgumentsFromSubscript` as it just feels too spooky for me for `method(obj)` to work, but `obj.method` to break. Saying that, I can't get your example to work. For example: `K>> nargout('containers.Map>containers.Map.isKey') Error using nargout Not a valid MATLAB file`. Any ideas? Thank you once again – rwb Apr 19 '16 at 14:21
  • Yea that won't work for `containers.Map` like you've shown because that's not a class. It *should* work for your class `nargout('Example>Example.sayHello')` and in the snippets I have below. – Suever Apr 19 '16 at 15:00
  • Thanks but that's a little annoying as in my actual code Example is namespaced (`foo.bar.Example`, for example) - for which I can't get to seem the `nargout` trick to work :( EDIT: What may be worth mentioning, is that I have noticed if I don't overload `numArgumentsFromSubscript` and stick a breakpoint in `subsref` when calling the struct example above, `nargout` _is_ set to zero. It's just that i need the `numel(obj)` trick for other method calls. How frustrating! – rwb Apr 19 '16 at 15:18
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/109580/discussion-between-suever-and-rwb). – Suever Apr 19 '16 at 15:25

1 Answers1

3

So digging into this a little further, you have a few options for how to get around this behavior.

method(obj) Calling Convention

You could simply change the way that you're calling the class method. Instead of using the dot notation, you could simply use the standard function notation.

sayHello(ex)

%// vs.

ex.sayHello()

This will avoid calling subsref when calling a method. Also, this is actually the fastest way to call a method of a class in the current iteration of MATLAB's OOP. Additionally, this would require no changes to your current code.

Use nargout to determine number of method outputs

Another option is to add a special case in subsref or numArgumentsFromSubscript that looks for methods called using the dot notation. Then you can explicitly determine the number of output arguments of the method using the following call to nargout.

nArgs = nargout('classname>classname.methodname');

Then you would use that rather than numel(obj). This could be implemented in either numArgumentsFromSubscript or subsref.

numArgumentsFromSubscript

function n = numArgumentsFromSubscript(obj, struct, indexingContext)
    %// Check if we are calling obj.method
    if strcmp(struct(1).type, '.') && ...
        ischar(struct(1).subs) && ismember(struct(1).subs, methods(obj))

        %// Determine the package (if any)
        cls = class(obj);
        parts = regexp(cls, '\.', 'split');

        %// Import the package (if any) just into the namespace of this method
        if numel(parts) > 1
            import(cls);
        end

        %// Determine number of outputs for this method
        n = nargout(sprintf('%s>%s.%s', parts{[end end]}, struct(1).subs));
    else
        %// Default to numel(obj)
        n = numel(obj);
    end
end

subsref

function varargout = subsref(obj, struct)
    if strcmp(struct(1).type, '()')
         [varargout{1:nargout}] = builtin('subsref', obj.map, struct);

    %// Check if we are calling obj.method
    elseif strcmp(struct(1).type, '.') && ...
           ischar(struct(1).subs) && ismember(struct(1).subs, methods(obj))

        %// Determine the package (if any)
        cls = class(obj);
        parts = regexp(cls, '\.', 'split');

        %// Import the package (if any) just into the namespace of this method
        if numel(parts) > 1
            import(cls);
        end

        %// Determine number of outputs for this method
        nout = nargout(sprintf('%s>%s.%s', parts{[end end]}, struct(1).subs));         

        %// Call builtin subsref with this number of outputs
        [varargout{1:nout}] = builtin('subsref', obj, struct);

    else
        [varargout{1:nargout}] = builtin('subsref', obj, struct);
    end
end

Summary

subsref is a pain and a lot of times you end up with a lot of logic to determine what is being called and you'll be right only about 80% of the time. I think that the first approach is the most straightforward, would likely be the most performant (you skip all of the checks in subsref) and deals better with arrays of objects.

If you really wanted to keep the obj.method notation, I would probably recommend changing numArgumentsFromSubscript (rather than subsref) to keep the number of output argument stuff in one place.

Update

Based upon your feedback that the nargout trick doesn't work in cases where the class is contained within a package (foo.bar.Example). I have added a workaround to the above examples. The trick is to call import('foo.bar.Example') prior to calling nargout('Example>Example.sayHello').

Community
  • 1
  • 1
Suever
  • 64,497
  • 14
  • 82
  • 101
  • 2
    I don't know what I'm more taken aback by - the effort taken in producing such a detailed answer, or the unwieldiness of Matlab's OOP! Thank you for all your help – rwb Apr 20 '16 at 13:16
  • I know this is an old post, but MATLAB OOP is not much more than a clunky Mathworks afterthought. I have been assigned a project in MATLAB and all I want to do is wrap a ``Map`` and overload the ``()`` indexing operators. How is it that C++ is much easier than MATLAB? – trozzel May 20 '21 at 12:13