当我重载subsref(下标引用)时,为什么MATLAB会抛出“太多的输出参数”错误?

时间:2013-12-31 20:51:05

标签: matlab operator-overloading

作为玩具示例,我有一个类,它只是将一个向量或矩阵包装在一个对象中,并包含一个创建时间的时间戳。我正在尝试重载subsref以便

  1. ()引用的工作方式与标准向量和矩阵类型完全相同
  2. {}引用在完全中的工作方式与()引用相同(换句话说与细胞无关)
  3. .引用允许我访问对象的私有属性以及非技术属性的其他字段。
  4. 代码:

    classdef TimeStampValue
    
        properties (Access = private)
            time;
            values;
        end
    
        methods
            %% Constructor
            function x = TimeStampValue(values)
                x.time = now();
                x.values = values;
            end
    
            %% Subscripted reference
            function x = subsref(B, S)
                switch S.type
                    case '()'
                        v = builtin('subsref', B.values, S);
                        x = TimeStampValue(v);
                    case '{}'
                        S.type = '()';
                        v = builtin('subsref', B.values, S);
                        x = TimeStampValue(v);
                    case '.'
                        switch S.subs
                            case 'time'
                                x = B.time;
                            case 'values'
                                x = B.values;
                            case 'datestr'
                                x = datestr(B.time);
                        end
                end
            end
    
            function disp(x)
                fprintf('\t%d\n', x.time)
                disp(x.values)
            end   
    
        end
    
    end
    

    然而,大括号{}引用不起作用。我运行此代码

    clear all
    x = TimeStampValue(magic(3));
    x{1:2}
    

    我收到此错误:

    Error using TimeStampValue/subsref
    Too many output arguments.
    Error in main (line 3)
    x{1:2} 
    

    MException.last告诉我这个信息:

    identifier: 'MATLAB:maxlhs'
       message: 'Too many output arguments.'
         cause: {0x1 cell}
         stack: [1x1 struct]
    

    这没有帮助,因为异常堆栈中唯一的东西是包含我在上面运行的三行代码的文件。

    我在subsref的switch语句的第一行放置了一个断点,但MATLAB从未到达它。

    这笔交易是什么? ().引用工作正如您所期望的那样,为什么{}没有引用工作?

4 个答案:

答案 0 :(得分:9)

当重叠大括号{}以返回比平常更多数量的输出参数时,还需要重载numel以返回预期的数字(在本例中为1)。 更新:从 R2015b 开始,新功能 numArgumentsFromSubscript 已创建为重载而非numel。问题仍然存在,但是这个函数应该重载而不是numel,正如我在下面的原始答案中描述的那样。另请参见页面"Modify nargout and nargin for Indexing Methods"。摘录:

  

当一个类重载numArgumentsFromSubscript时,MATLAB调用此方法而不是numel来计算subsref nargoutsubsasgn {{的预期参数数量1}}。

     

如果类没有重载nargin,MATLAB会调用numArgumentsFromSubscript来计算numelnargout的值。

对基础问题的更多解释(需要指定输出参数的数量)如下。


原始回答(使用nargin代替numArgumentsFromSubscript进行R2015b +)

为了在使用花括号进行索引时处理逗号分隔的输出参数列表的可能性,MATLAB调用numel以根据输入索引的大小确定输出参数的数量(根据this MathWorks answer )。如果重载numel定义中的输出参数数与subsref提供的数字不一致(即小于),则会出现“输出参数太多”错误。如MathWorks所述:

  

因此,为了允许大括号索引到您的对象,同时返回大量具有输入大小的参数INCONSISTENT,您将需要重载类目录中的NUMEL函数。

由于numel通常提供两个输出(x{1:2}),因此定义X{1},X{2}与此输入不兼容。解决方案是在类中包含一个简单的function x = subsref(B, S)方法来重载内置函数,如下所示:

numel

现在,function n = numel(varargin) n = 1; end 索引按预期工作,模仿{}

()

但是,以这种方式重载花括号是apparently“我们[MathWorks]不希望客户写的特定类型的代码”。 MathWorks建议:

  

如果您设计的类只输出一个参数,则不建议您使用大括号索引来要求您重载NUMEL。相反,建议您使用smooth brace()索引。

更新:有趣的是,R2015b release notes state

  

在MATLAB发布R2015b之前,对于返回或分配给逗号分隔列表的某些索引表达式,MATLAB错误地计算了>> clear all % needed to reset the class definition >> x = TimeStampValue(magic(3)); >> x(1:2) ans = 7.355996e+05 8 3 >> x{1:2} ans = 7.355996e+05 8 3 输出和subsref输出的预期参数数。

     

在版本R2015b中,MATLAB根据索引表达式所需的参数数量正确计算subsasgnnargout的值。

所以也许现在已经修好了?


想到的另一种解决方案是将nargin更改为function x = subsref(B, S)并添加function varargout = subsref(B, S)。正如Amro在评论中指出的那样,预先分配单元格是必要的,以避免关于未分配参数的错误。

答案 1 :(得分:1)

我遇到了同样的问题。更糟糕的是,输出参数的数量被强制等于numel()不仅返回花括号{},还返回点.操作。

这意味着如果重写numel()以返回通常的prod(size(obj)),则无法访问基础对象的任何属性(例如上例中的x.time),如然后,subsref()将返回多个输出。

但如果numel()只返回1,则它与prod(size(obj))不匹配,这是大多数代码使用数值或基于reshape()所期望的。事实上,MATLAB编辑器的气球帮助立即表明'NUMEL(x)通常比PROD(SIZE(x))更快,这表明它们是等价的,但显然不是。

一种可能的解决方案是让numel()返回prod(size(obj))并为所有这些属性编写显式的getter / setter函数,例如,

x.get_time()

在上面的例子中。这似乎有效,因为在subsref()被调用之前,方法调用显然得到了解决。但是,如果其中一个属性是矩阵,则不能再直接索引,因为Matlab不理解链式索引,即代替编写

x.matrix(1,:)

一个人必须写

m = x.get_matrix();
m(1,:)

至少可以说是丑陋的。

这开始有点令人沮丧。我仍然希望我只是忽略了一些显而易见的事情,我无法相信它应该如何发挥作用。

答案 2 :(得分:0)

此解决方案似乎在2014b中有效(但不完全确定原因)

classdef TestClass < handle
    methods

        function n = numel(~,varargin)
            n = 1;
        end

        function varargout = subsref(input,S)
            varargout = builtin('subsref',input,S);
        end

        function out = twoOutputs(~)
            out = {}; out{1} = 2; out{2} = 3; 
        end
    end
end

然后通过命令窗口

>> testClass = TestClass();
>> [a,b] = testClass.twoOutouts()
a =

     2

b =

     3

答案 3 :(得分:0)

我正在研究一个处理多项式和多项式矩阵的类。我有同样的困难,因为在标量多项式和多项式矩阵的情况下,我想要'.'索引的不同行为。

在我的情况下,如果P.coef是标量多项式,我希望P返回系数向量。如果P是多项式矩阵,P.coef必须返回大小为P的单元格数组,其中单元格{i,j}包含多项式P(i,j)的系数向量1}}。

P.coef与矩阵一起使用时出现问题。我想要的行为只返回一个对象作为答案,但Matlab期望该函数返回numel(P)个对象。

我找到了一个非常简单的解决方案。声明subsref时,我使用了一个强制性输出和一个varargout

function [R,varargout] = subsref(P,S) 

根据我的设计,函数的主体根据需要定义R。在函数的最后,我添加了:

varargout(1:nargout-1) = cell(1,nargout-1);

只返回空矩阵作为Matlab想要的额外输出。

如果始终使用单个输出参数调用函数,则不会产生任何问题,例如,如R = P.coef中所示。如果在没有分配的情况下调用该函数,则用户将看到numel(P)-1空矩阵,这实际上并不是什么大问题。无论如何,在功能帮助中会向用户发出警告。