codeAndQuote codeAndQuote - 1 month ago 14
C++ Question

Default return value for a template variable

I have a template link list implementation in which

T List<T>::GetElementFromHead()


returns value from the head of the list. What value should be returned when the list is empty.

template <class T>
T List<T>::GetElementFromHead()
{
T element;
if (!IsEmpty())
{
element = Head->value;
}
return element;
}


If
IsEmpty()
returns true then
return element;
throws exception.

How do I return null or and empty value in this case?

Answer

What value should be returned when the list is empty.

There are two reasonable options.

  1. Who cares. Set as a precondition that GetElementFromHead() shall only be called if the list isn't empty, and then you can just write:

    template <class T>
    T& List<T>::GetElementFromHead() {
        return Head->value;
    }
    

    This has several nice things about it. No copies. It's fast. I can modify the value if I want to. No extra branch, since you're requiring the user to do it up front. No need to worry about what the error case is or coming up with a reasonable sentinel value. If you return T{}, what if T is int and the list can have 0 normally - how does the user differentiate between a "real" 0 and a "fake" 0?

    This is how the standard library implements list::front(), vector::front(), etc. Do as the standard library do is usually a good decision.

  2. Encapsulate in the return value itself whether or not it's a real value:

    template <class T>
    std::optional<T> List<T>::GetElementFromHead() {
        if (!IsEmpty()) {
            return Head->value;
        }
        else {
            return std::nullopt;
        }
    }
    

    (If using boost::optional, the last line should return boost::none). This way, the return value itself makes it clear what situation we're in - we either have a value and that value is the front of the list, or we don't have a value. Again, no need to come up with a sentinel.

    An alternative version of this would be to return a T*, and nullptr on failure. This has the advantage that it doesn't incur a copy or require that T be copy constructible.

Comments