tags:

views:

337

answers:

9

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?

+1  A: 

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);
    }
};
Motti
+6  A: 

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.

unwind
Why not use NULL? To me it's clearer that you're dealing with pointers iso. integers that way.
Kristof Provost
+1 for the suggestion to return an 'end' iterator
Wilka
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.
AProgrammer
@Kristof: Stroustrup makes a fairly marginal argument against - http://www.research.att.com/~bs/bs_faq2.html#null
Steve Jessop
@Kristof: I added that very link to the answer itself, before seeing it was alraedy here in your comment. I'll leave it, but thanks!
unwind
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.
Loadmaster
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.
Steve Jessop
You say "don't use NULL", linking to stroustrup's statement where he says "In C++, the definition of NULL is 0, so there is only an aesthetic difference.". Looks odd - i'm sure stroustrup wouldn't say "don't use NULL".
Johannes Schaub - litb
+11  A: 

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
}
Xeor
I am not using boost so can't use boost::none
Aamir
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).
AProgrammer
+2  A: 

You should return T* non T

Preet Sangha
+1  A: 

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; 
}
Alan
+1, I would have done that too.
Naveen
-1 Except that it doesn't work, because the function takes `item` per copy.
sbi
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.
UncleBens
+2  A: 

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

sbi
A: 

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

mos
+1  A: 

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

Partial
A: 

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.

camh