6

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?

asked Sep 8, 2009 at 9:09
5
  • 1
    Its a compile time error. Not a run-time. Commented Sep 8, 2009 at 9:23
  • 1
    Run-time for std::string, std::string(const char*) will be called Commented Sep 8, 2009 at 9:27
  • @Naveed: I am using Visual Studio 2005's VC compiler and it is not a compile time error in it. Can't say about gcc. Commented Sep 8, 2009 at 9:29
  • Oops..sorry my mistake. Yes it crashes in VS2008 also. Commented Sep 8, 2009 at 9:34
  • 1
    Initializing a std::string with NULL is not a compile-time error, but will invoke undefined behavior, likely failing at run-time. Commented Sep 8, 2009 at 9:34

10 Answers 10

15

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
}
answered Sep 8, 2009 at 9:12
Sign up to request clarification or add additional context in comments.

2 Comments

I am not using boost so can't use boost::none
If you want to return a singular value for a type which don't have one, you'll have to use optional or something equivalent. If you don't have access to boost, you can still define yours (another common name used for it is Fallible).
9

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.

answered Sep 8, 2009 at 9:11

7 Comments

Why not use NULL? To me it's clearer that you're dealing with pointers iso. integers that way.
The use or not of NULL is a matter of coding style right next to the position of curcly brackets or the number of spaces to be used when indenting. I've used both, I don't prefer one over the other as long as it is used consistently. There are arguments both way, I don't feel any I've heard of compelling.
@Kristof: Stroustrup makes a fairly marginal argument against - research.att.com/~bs/bs_faq2.html#null
The fact that C++ lacks a 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.
Well, the non-existence of the null keyword (to be rectified in C++0x) is a symptom of the fact that pointers and integers can be implicitly converted. I think that's a worse failing (and GCC's authors agree, since there's a warning for it), but it's inherited from C and doing away with it would presumably be too big a compatibility break for the C++ committee to stomach.
|
7

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.

answered Sep 8, 2009 at 9:31

Comments

3

You should return T* non T

answered Sep 8, 2009 at 9:16

Comments

2

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);
 }
};
answered Sep 8, 2009 at 9:11

Comments

2

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).

answered Sep 8, 2009 at 15:25

1 Comment

This doesn't work if 0 is a valid return value. atoi() has the same problem.
1

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; 
}
answered Sep 8, 2009 at 9:16

2 Comments

-1 Except that it doesn't work, because the function takes item per copy.
If you return a copy of the original item when it is found (as the comment in asker's code shows), you might just as well return a bool. The caller already has the item.
1

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...

answered Sep 8, 2009 at 21:51

Comments

1

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.

answered Sep 9, 2009 at 13:11

Comments

0

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

  1. 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\"\\\\\\\\\\";
  1. Exceptions (as @Partial explained), or

  2. Even going so far as to somehow use a Wrapper Class to expand the type's domain (as @camh noted). Using ssize_t comes to mind for error-value/default-value types, but it is still limited in that some datatypes are simply incompatible with ssize_t, like std::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
}
answered Sep 13, 2023 at 7:28

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.