[Development] API for multiple (but variable number of) return arguments

Philip Ashmore contact at philipashmore.com
Sat Jul 27 14:05:12 CEST 2013


Hi there.

On 27/07/13 10:36, Andre Somers wrote:
> Hi,
>
> It has been quite awhile, but I'd like to finally pick up QTimeSpan 
> again and this time really get it into Qt. The idea of this class is 
> that it represents a duration of time, optionally anchored to a point in 
> time, and can output and parse this data to and from strings in 
> different formats, including using 'natural' units like years or months 
> for long time spans. One of the thing I'd like to do is shrink and 
> simplify the API before submitting it.
>
> It is not the idea of QTimeSpan in itself that I'd like to discuss, but 
> one API issue I'm currently looking at.
>
> For the sake of discussion, let's assume that we have the following already:
> namespace Qt {
>    enum TimeSpanUnit {
>      Years = 0x01,
>      Months = 0x02,
>      Weeks = 0x04,
>      Days = 0x08,
>      Hours = 0x10,
>      Minutes  = 0x20,
>      Seconds = 0x40,
>      MilliSeconds = 0x80
>    };
>    Q_DECLARE_FLAGS(TimeSpanFormat, TimeSpanUnit);
> };
>
> In order to get a time span in the units that you'd like to use, you 
> need a function that can somehow return the values for all these units 
> at the same time, because in order to calculate the number of days, you 
> also need to know if the user also requested the number of weeks 
> (compare "23 days" with "3 weeks, 2 days"). Ideally, there would be a 
> return value as well to indicate success or failure, because time spans 
> without an anchor date cannot be expressed as months or years.
>
> There are several ways to write a (member) function to do this. This is 
> what I came up with so far:
>
> 1) pointers to ints
> bool getUnits(int* years, int* monts = 0, int* weeks = 0, int* days = 0, 
> int* hours = 0, int* minutes = 0, int* seconds = 0, int* milliSeconds = 0);
You could wrap this into a struct:
   struct TimeSpanResult {
      int m_years, m_months, m_weeks, m_days, m_hours, m_minutes,
m_seconds, m_milliseconds;
   };

and return it from the call:
TimeSpanResult getUnitsTimeSpan(Qt:Minutes | Qt:Seconds);

I just used a different name as the return type can't be overloaded.


TimeSpanResult getUnitsTimeSpan(int flags)
{
    TimeSpanResult res;
    getUnits((flags & Qt:Years) ? & res.m_years : (int *)0, (flags &
Qt:Years) ? & res.m_months : (int *)0, ...
    return res;
}
I've left out error checking for clarity.
>
>
> Every argument may be 0, in which case this unit is considered as 
> not-used. This works (I have this in the current version of QTimeSpan), 
> but it is not a very nice API to work with. If you're interested in 
> minutes and seconds, you end up with a call like this:
>
> int minutes, seconds;
> if (getUnits(0,0,0,0,0, &minutes, &seconds))
>    { ...}
>
> 2) returning a map
> QMap<Qt::TimeSpanUnit, int> getUnits(Qt::TimeSpanFormat);
>
> In this case, the units to use are encoded in a single flag, and the 
> result is a single data structure. I guess the easiest way to return 
> failure is to return an empty map. The equivalent call from the first 
> option would look like:
>
> int minutes, seconds;
> QMap<Qt::TimeSpanUnit, int> result = getUnits(Qt::Minutes | Qt::Seconds);
> if (!result.isEmpty()) {
>    minutes = result.value(Qt::Minutes);
>    seconds = result.value(Qt::Seconds);
> };
>
> In my opinion, not all that great either, but better than 1). It may be 
> a benefit to have the results in a data structure that's easy to pass 
> along in one go, but if you don't want that, getting them out is a bit 
> of a hassle perhaps.
>
> 3) QString::arg like API
> An interesting option is to do something like this:
>
> int minutes, seconds;
> if ( getUnits().unit(Qt::Minutes, minutes).unit(Qt::Seconds, seconds) ) {
>    //blah
> };
>
> Downside is that it allows specifying the same unit multiple times. That 
> does not need to be a real problem (simply return the same value more 
> than once), but it may look weird. Also, this API is implementation wise 
> quite a bit more complex than the previous options. The resulting code 
> is more verbose than option 1, but much easier to read.
>
> 4) separate requests
> Another option is not return all relevant units in one go, but to use 
> multiple function calls:
> int getUnit(Qt::TimeSpanFormat format, Qt::TimeSpanUnit unit);
>
> Failure would return -1. The example would yield:
>
> Qt::TimeSpanFormat format = Qt::Minutes | Qt::Seconds;
> int minutes = getUnit(format, Qt::Minutes);
> int seconds = getUnit(format, Qt::Seconds);
> if (minutes >= 0 && seconds >=0) {
>    //blah
> }
>
> Downside is that in order to make this efficient, you'd need to cache 
> the result, otherwise you have to reconstruct it for every call with the 
> same format. Personally, I don't like having to make multiple function 
> calls to retrieve parts of the same answer. Also, there are 
> opportunities for errors here, such as asking for units that are not in 
> the format.
>
> I guess there are more options, but these four are the more serious 
> candidates I could come up with. Did I overlook a good candidate? Where 
> are other examples in the Qt API where there were similar needs, so I 
> can look if their patterns may apply here?
>
> Anyway, I am looking for feedback. Say that this will be included in Qt 
> some day, what is the most Qt-like API in your opinion?
>
> André
>
> _______________________________________________
> Development mailing list
> Development at qt-project.org
> http://lists.qt-project.org/mailman/listinfo/development
Regards,
Philip Ashmore



More information about the Development mailing list