I have a method which looks like this:
template <typename T>
T Test<T>::FindItem(T item)
{
if(found)
//return original value, no problem here
else
//I want to return NULL here, like:
return NULL;
}
This fails in certain cases at runtime because some of the values can't be converted to NULL in C++ e.g., std::string. What approach should I follow here?
10 Answers 10
If you want to return by value and don't want to mess with returning pointers and new/delete, you can just do like this:
template <typename T>
boost::optional<T> Test<T>::FindItem(T item)
{
if(found)
//return original value
else
return boost::none;
}
and use it this way:
Test<int> var;
boost::optional<int> V = var.FindItem(5)
if (V)
{
// found
int val = *V;
}
else
{
// not found
}
2 Comments
Here, Stroustrup gives a couple of arguments for avoiding NULL in C++:
- The definition is just
0, so there's no actual gain [and more to type] - It's a macro, and macros are best avoided in C++
The suggestion, again from that page is:
- Use a plain
0, or (for C++0xwhatever)nullptr.
Second, if you want to return an invalid pointer as a signal of failure, you need to declare the function as returning a pointer to a type, not the type itself.
Third, most standard library find() functions return iterators, rather than elements directly. That gives you the possibility to implement the "nothing found" case differently.
7 Comments
null keyword is a failing of the language design, in spite of any arguments Stroustrup has given. Pointers are different than integers, period, and so should be syntactically different.You have a function that might return a valid object, but also an indication that no valid object exists. Combined with a situation where all the possible object values are valid, this is a design problem with your find function. (Even if you tweaked the function so that it would, for example, return an empty string if it doesn't find one, how would this be distinguishable from successfully finding an empty string?)
Over time, several ways out of this dilemma were found. So you could return a pointer/iterator instead of returning the actual value. Then a NULL pointer/end iterator would indicate "not found". Or you could take a value by non-const reference, assign the result to it and indicate success/failure by a returned boolean. Or take one of the other solutions posted here. But all will require that you change the function's interface -- because as it is it has a design error.
Anyway, before you jump in and do this, you should ask yourself why you write your own container/algorithm instead of using one from the std lib.
Comments
You should return T* non T
Comments
Most types are default constructable you have have a traits template class that can be specialized (with the default returning a default initialized object).
template <class T>
struct Make {
static T Default()
{
return T();
}
};
class not_default_constructable {
public:
explicit not_default_constructable(int i) {}
};
// specialize for not_default_constructable
template <>
struct Make <not_default_constructable> {
static not_default_constructable Default()
{
return not_default_constructable(42);
}
};
Comments
Return T(). If T is of type sometype *, it will be initialized to a null pointer. If T is of type sometype, it will return an object created with the default constructor, which should suffice (std::string will be empty, integral types like ints will be 0, etc).
Maybe I'm being too simplistic, but I prefer simply returning a bool:
template <typename T>
bool Test<T>::FindItem(T item)
{
if (!found)
return false;
item = <found item>;
return true;
}
In my opinion, you are trying to complicate yourself a bit by trying to return a NULL value...
Instead do something similar to this:
class SomeError
{};
template <typename T>
T Test<T>::FindItem(T item)
{
if(found)
//return original value, no problem here
else
throw(SomeError);
}
Throwing an empty class does not cost anything...
Comments
You could throw an exception when it is not found.
Since you have no knowledge of the type being looked-up and returned, you can not designate any particular value of that type as an error code. In essence, you are trying to squeeze the full domain of a type plus an error value into the domain of the type.
The suggestions to use iterators or other wrappers help solve the problem by extending the domain of the return type to encompass error values, but if you want to stick to the actual types you are dealing with, exceptions are you best other choice. That's what they're for.
Comments
Considering how none of the existing answers from 2009 (~14 years ago) cover this, I'm guessing this feature did not exist in any form around then (also considering the response that C++ doesn't contain a null keyword, which is obviously false nowadays and possibly back then too).
I assume that casting NULL into a templated type still gets represented as 0, so having a legal value 0 (i.e., a possible output value) and a default value of 0 (via (T)NULL) is obviously still an issue, one that I am unsure how to resolve except by giving up on the templated type altogether and reverting to handling each case for each type via
- User-Defined Enumerations (hardcoded and widely-used-across-your-project variable names like
int DEFAULT_VAL_INT = INT_MIN+1;
std::string BAD_QUALITY_STR =
"***thereIsNoChanceOfThisStringEverHappeningNaturallyExceptForMonkeysOnTypewriters\"\\\\\\\\\\";
Exceptions (as @Partial explained), or
Even going so far as to somehow use a Wrapper Class to expand the type's domain (as @camh noted). Using
ssize_tcomes to mind for error-value/default-value types, but it is still limited in that some datatypes are simply incompatible withssize_t, likestd::string.
Here is C++ code (compiled using C++11 and MINGW64) for a Destructor (I've heard it called a Deconstructor too):
stack.hpp
template<class T>
struct StackNode {
T value;
StackNode<T> *next;
StackNode<T> *prev;
StackNode(T value, StackNode<T> *next, StackNode<T> *prev);
~StackNode();
};
stack.cpp
//Wipes variable values, then deallocates at the end of the destructor
template<class T>
StackNode<T>::~StackNode() {
this->nextNode = this->prevNode = nullptr;
this->value = (T)NULL; //explicitly type-casted NULL
}
std::stringwithNULLis not a compile-time error, but will invoke undefined behavior, likely failing at run-time.