When there is nothing to return from a value-returning function!

Consider this, guys.
For example?
Consider the function below which returns the kth element in a list. How do I return, at least, something from the function when the kth element is NOT found in the list? When I ran the program of which the function is a part of, the message in the else block was indeed output to the display, but, the program execution froze as subsequent statements in the program after the function call could not be executed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<class elemType>
const elemType& linkedListType<elemType>::kthElement(int k)
{
	int i = 0; 
	
	if (k <= count)
	{	
		nodeType<elemType>* current;
		
		current = first;
		while (i < k)
		{
			current = current->link;
			i++;
		}	
		return current->info;		
	}
	else
		cout<<"List does not have "<<k<<" elements!"<<endl;	    
}


By the way, I do not want to use a void function!
A function that returns a reference must always return something valid. If this is not possible because the preconditions of the function have not been met by the caller, aborting, throwing, or causing undefined behavior are all acceptable. The latter is what std::vector::operator[]() does.

If a function may, for reasonable inputs, be unable to return something valid then the function should not return a reference at all.
There's a myriad of solutions for the problem of empty return value. A few are:
* The function returns an iterator for a sequence and returns an iterator to one past the end of the sequence to indicate empty.
* The function returns a pointer and nullptr to empty.
* The function returns a smart pointer and a default-constructed smart pointer for empty.
* The function returns a signed type but implements an algorithm whose domain is in the positives, so returns a negative for empty.
* The function returns a bool or some other type for signalling, and writes the real output to a reference or pointer passed as a parameter, setting aside one or more values of the return type to indicate failure. This is often used in OS APIs.
geeloso wrote:
Consider this, guys: When there is nothing to return from a value-returning function!
Thrown an exception or return a boost::optional/std::experimental::optional/std::optional.
> return a boost::optional/std::experimental::optional/std::optional.

For a function that needs to return an lvalue, std::experimental::optional is not an option.

Repeat:
Please try to understand the difference between value semantics and reference semantics.
std::experimental::optional<T> holds an optional (non-polymorphic) value of type T.

"A program that necessitates the instantiation of template optional for an lvalue reference or rvalue reference type ... is ill-formed."

http://www.cplusplus.com/forum/general/171734/#msg854899
http://www.cplusplus.com/forum/beginner/166841/#msg839293

The distinction between value semantics and reference semantics is fundamental to C++; I fail to fathom why people find this so hard to understand.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <experimental/optional>

struct base
{
    virtual ~base() = default ;
    virtual void foo() { std::cout << "base::foo\n" ; }
};

// *p is an lvalue of type reference to base
void bar_simple_and_effective( base* p = nullptr ) { if(p) p->foo() ; else { /* whatever */ } }

// b.value() is an xvalue (rvalue) of type base
void bar_convoluted_and_broken( std::experimental::optional<base> b ) { if(b) b.value().foo() ; else { /* whatever */ } }

struct derived : base
{
    virtual void foo() override { std::cout << "*** overridden *** derived::foo\n" ; ++v ; }
    int v = 99 ;
};

int main()
{
    derived d ;
    bar_simple_and_effective( std::addressof(d) ) ; // *** overridden *** derived::foo
    bar_convoluted_and_broken(d) ; // base::foo (operates on a sliced copy)
}

http://coliru.stacked-crooked.com/a/4f8d8176c0df6777
I don't find it hard to understand, I just forget that optional can't be instantiated with a reference type. It seems an unusual decision to me.
Last edited on
boost::optional<> has partial support for optional references: lvalue references are allowed, rvalue references are not. (At the time it was designed, support was complete: back then, there were no rvalue references; there was no generic code using universal references)

In conjunction with std::experimental::optional<>, if optional lvalue references are, for some reason unavoidable (raw pointers are 'evil'; the keyword this should never be used etc.), an optional wrapped reference could be used:
std::experimental::optional< std::reference_wrapper<T> >

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <boost/optional.hpp>
#include <boost/version.hpp>
#include <experimental/optional>
#include <functional>

struct base
{
    virtual ~base() = default ;
    virtual void foo() { std::cout << "base::foo\n" ; }
};

void bar( boost::optional<base&> b ) { if(b) b->foo() ; else { /* whatever */ } }

// void bar( boost::optional<base&&> b ) ; // *** error: optional rvalue reference is not allowed

void bar( std::experimental::optional< std::reference_wrapper<base> > b ) 
{ if(b) b.value().get().foo() ; else { /* whatever */ } }

struct derived : base
{
    virtual void foo() override { std::cout << "*** overridden *** derived::foo\n" ; ++v ; }
    int v = 99 ;
};

int main()
{
    derived d ;
    bar(d) ;
    bar( std::ref<base>(d) ) ;

    // bar( derived{} ) ; // *** error

    std::cout << "boost lib version " << BOOST_LIB_VERSION << '\n' ;
}

http://coliru.stacked-crooked.com/a/d3a0cc7892a3f03a
This boost stuff is way over my head, guys! I am teaching myself C++. I checked the Reference section of this website, but I couldn't find it among the header files there. Can anyone, in plain language, just explain what it is all about? Or better still, supply links to helpful/instructive literature on the concept.

@ JLBorges:
Thanks for your example codes but I find it hard to see how it resolves/deals with the problem I described earlier. Probably, it's due to my limited(/lack of) knowledge about boost. I tried throwing an exception when the kth element is not in the list; although it worked, but it halted the execution of subsequent statements in the program. I don't want that! Or am I asking for too much here and the only way out is to redefine/restructure the function??
geeloso wrote:
This boost stuff is way over my head, guys! I am teaching myself C++. I checked the Reference section of this website, but I couldn't find it among the header files there. Can anyone, in plain language, just explain what it is all about?
Boost is a popular library for C++ and contains many, many sub-libraries that all do different things. Boost.Optional is just one of those sub-libraries:
http://www.boost.org/doc/libs/1_59_0/libs/optional/doc/html/index.html
Parts of Boost are slowly being pulled into the C++ Standard Library, so in the future we will have std::optional and you won't need to use Boost for that particular thing.

geeloso wrote:
I tried throwing an exception when the kth element is not in the list; although it worked, but it halted the execution of subsequent statements in the program. I don't want that!
You want to eventually catch the exception.

You only throw exceptions where you don't know how to recover from an error.
You only catch exceptions where you do     know how to recover from an error.
Last edited on
Thx LB for your quick reply and succinct explanation on boost. Regarding the exception, yeah, I had the exception thrown and caught, and yet, the program still halted the way I already described. I had the try and catch blocks both in the function as shown below, alongside a derived class from the std::exception base class:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class outOfRangeException: public exception
{
    virtual const char* what() const throw()
    {
        return "The List does not have that many elements!";
    }
} error;


template<class elemType>
const elemType& linkedListType<elemType>::kthElement(int k)
{
    int i = 0; 

    try
    {
    	if (k <= count)
    	{	
    	        nodeType<elemType>* current;
    		
    		current = first;
    		while (i < k)
    		{
    			current = current->link;
    			i++;
    		}		
    		return current->info;		
    	}
    	else
    	{
    		throw error;
    	}
    }
    catch (exception& e)
    {
        cout<<e.what()<<endl<<endl;
    }    
}
After you caught exception, execution resumes normal state. That means you still need to return value from function.

You want to let exception to escape current scope and catch it on the caller side:
1
2
3
4
5
6
7
8
linkedListType<int> x;
//...
try {
    int i = x.kthElement(INT_MAX);
    // Use i
} catch (const std::exception& e) {
    std::cout << e.what() << "\n\n";
}
> I find it hard to see how it resolves/deals with the problem I described earlier.

It was not intended to be a solution to your earlier problem; I was merely pointing out that returning an optional value wrapped in std::experimental::optional<> is an inappropriate solution to the problem.

Throwing an exception is a sane solution; the one hat I would recommend.
@ MiiNiPaa:

So you're suggesting that I put the try and catch blocks in function main() instead of in the function?
Last edited on
Yes. That how standard library .at() members work: they throw an exception on invalid access and let caller handle it.
Thx MiiNiPaa! For some reason, your suggestion resolved the problem. However, I am still skeptical whether the problem was solved based solely on this statement you made:
You want to let exception to escape current scope and catch it on the caller side:

At first glance, it would seem as if the problem was resolved based on this since, now, an exception is thrown in function const elemType& linkedListType<elemType>::kthElement(int k) and caught in function main(). But let me say that I have seen some examples where an exception is thrown and caught in the same function/scope (e.g. in main()).

How do you reconcile these apparent inconsistencies?
An exception should never be thrown and caught in the same scope, that is bad design and pointless. You may have seen it in examples demonstrating exception handling - remember that examples of a particular concept are often not examples of good practice ;)
Topic archived. No new replies allowed.