codeAndQuote codeAndQuote - 10 months ago 69
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 Source

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.