Search code examples
matlabvalidationoopargumentsinstantiation

Subclass constructor refuses to accept any name-value arguments


TL;DR

I am having an issue with the arguments functionality, wherein a subclass fails to instantiate if any name-value argument pairs are passed to it.

(Class definitions are given at the end.)


The arguments functionality, introduced in R2019b, brings great promise in terms of simplifying argument validation and removing boilerplate code from functions [1]. However, upon trying to implement name-value (NV) arguments taken from public class properties, I got the following error:

Invalid argument list. Check for wrong number of positional arguments or placement of positional arguments after 
name-value pairs. Also, check for name-value pairs with invalid names or not specified in pairs. 

before any subclass code is even executed. This message is confusing, because tab-completion appears to work as expected for NV pairs:

enter image description here

Moreover, if no arguments are passed at all, everything works fine:

>> FluidLayer()
ans = 
  FluidLayer with properties:

    prop1: NaN
    prop2: NaN

vs.

>> FluidLayer('prop1',1)
Error using FluidLayer
Invalid argument list. ...

Questions:

  1. Why am I getting an error? I don't think I'm using the arguments mechanism in some unintended or undocumented way, so I'm doubly puzzled about what I might be doing wrong (assuming this is not a bug).
  2. What can be done to resolve this, other than abandoning the whole arguments approach (I would like to keep argument name suggestions)? I have considered transitioning to varargin and/or using the functionSignatures.json approach - but these require significantly more work.

classdef Layer < handle & matlab.mixin.Heterogeneous

  properties (GetAccess = public, SetAccess = protected)
    prop1(1,1) double {mustBeNonempty} = NaN
    prop2(1,1) double {mustBeNonempty} = NaN
  end % properties

  methods (Access = protected)

    function layerObj = Layer(props)
      % A protected/private constructor means this class cannot be instantiated
      % externally, but only through a subclass.
      arguments
        props.?Layer
      end

      % Copy field contents into object properties
      fn = fieldnames(props);
      for idxF = 1:numel(fn)
        layerObj.(fn{idxF}) = props.(fn{idxF});
      end
    end % constructor
  end % methods

end
classdef FluidLayer < Layer

  properties (GetAccess = public, SetAccess = protected)
    % Subclass-specific properties

  end % properties

  methods 
    function layerObj = FluidLayer(props)
      arguments
        props.?FluidLayer
      end

      % Create superclass:
      propsKV = namedargs2cell(props);
      layerObj = layerObj@Layer(propsKV{:});

      % Custom modifications:

    end % constructor
  end % methods

end

Solution

  • I have been able to simplify your example to this:

    classdef Layer
    
        properties (GetAccess = public, SetAccess = protected)
            prop1
            prop2
        end % properties
    
        methods
    
            function layerObj = Layer(props)
                arguments
                    props.?Layer
                end
                disp(props)
            end % constructor
    
        end % methods
    
    end
    

    Now Layer('prop1',1) throws an error as you describe.

    Thus, it has nothing to do with subclassing or inheritance.

    However, if we remove the SetAccess = protected restriction (leaving the properties with public get and set access), then it all works as you expected.

    I don't know why restricting set access would limit this use case, as it has nothing to do with writing those properties, and a class method should have set access anyway. My guess is that this is a bug.