Search code examples
c++stringcontainersapi-designreadability

Is there a concise opposite of "empty"?


Interfaces to string classes typically have of method named IsEmpty (VCL) or empty (STL). That's absolutely reasonable because it's a special case, but the code that uses these methods often has to negate this predicate, which leads to a "optical (and even psychological) overhead" (the exclamation mark is not very obvious, especially after an opening parenthesis). See for instance this (simplified) code:

/// format an optional time specification for output
std::string fmtTime(const std::string& start, const std::string& end)
{
    std::string time;
    if (!start.empty() || !end.empty()) {
        if (!start.empty() && !end.empty()) {
            time = "from "+start+" to "+end;
        } else {
            if (end.empty()) {
                time = "since "+start;
            } else {
                time = "until "+end;
            }
        }
    }
    return time;
}

It has four negations, because the empty cases are those to be skipped. I often observe this kind of negation, also when designing interfaces, and it's not a big problem but it's annoying. I only wish to support writing understandable and easy-to-read code. I hope you'll understand my point.

Maybe I'm only struck with blindness: How would you solve the above problem?


Edit: After reading some comments, I think it's nessessary to say that the original code uses the class System::AnsiString of the VCL. This class provides an IsEmpty method, which is very readable:

 if (text.IsEmpty()) { /* ... */ } // read: if text is empty ...

if not negated:

 if (!text.IsEmpty()) { /* ... */} // read: if not text is empty ... 

...instead of if text is not empty. I think the literal is was better left to the reader's fantasy to let also the negation work well. Ok, maybe not a widespread problem...


Solution

  • In most cases you can reverse the order of the ifand the else to clean up the code:

    const std::string fmtTime(const std::string& start, const std::string& end)
    {
        std::string time;
        if (start.empty() && end.empty()) {
            return time;
        }
    
        if (start.empty() || end.empty()) {
            if (end.empty()) {
                time = "since "+start;
            } else {
                time = "until "+end;
            }
        } else {
            time = "from "+start+" to "+end;
        }
        return time;
    }
    

    Or even cleaner after some more refactoring:

    std::string fmtTime(const std::string& start, const std::string& end)
    {
        if (start.empty() && end.empty()) {
            return std::string();
        }
    
        if (start.empty()) {
            return "until "+end;
        }    
    
        if (end.empty()) {
            return "since "+start;
        }
    
        return "from "+start+" to "+end;
    }
    

    And for the ultimate compactness (although I prefer the previous version, for its readability):

    std::string fmtTime(const std::string& start, const std::string& end)
    {
        return start.empty() && end.empty() ? std::string()
             : start.empty()                ? "until "+end
             :                  end.empty() ? "since "+start
                                            : "from "+start+" to "+end;
    }
    

    Another possibility is to create a helper function:

    inline bool non_empty(const std::string &str) {
      return !str.empty();
    }
    
    if (non_empty(start) || non_empty(end)) {
    ...
    }