Thursday, November 4, 2021

Digest for comp.lang.c++@googlegroups.com - 7 updates in 3 topics

Frederick Gotham <no_such_address@no_such_domain91725425.com>: Nov 04 11:45AM

I'm writing a class called "MultiStack". It is a stack container in
which the elements can be of different types, for example you can push
an 'int' onto the stack, and then push a 'std::string' onto the stack.
 
This class shall have an interface as close as possible to "std::stack"
while accommodating new features. The following 7 public member
functions shall be implemented:
 
empty - Test whether container is empty
size - Return quanity of elements
pop - Remove top element
swap - Swap contents with another Multistack
push - Insert element
emplace - Construct and insert element
top - Access top element
 
Specifically the following four member functions will have the same
signature and behave exactly as they do for "std::stack":

empty, size, pop, swap
 
However, whereas the standard library container "std::stack" contains
elements of only one type, for example:
 
std::stack<int > my_multistack_of_ints ;
std::stack<float > my_multistack_of_floats ;
std::stack<std::string> my_multistack_of_strings;

The MultiStack container can have a unique type for each element,
for example:
 
int my_int = 5;
double my_double = 67.45;
std::string my_str("I like green monkeys");
 
MultiStack my_multistack;
my_multistack.push(my_int );
my_multistack.push(my_double);
my_multistack.push(my_str );
my_multistack.emplace<int>(65535);
my_multistack.emplace<std::string>(20u, 'a');
 
I'm sure you can guess how I implemented the "push" and "emplace" member
functions, quite simply as templates:
 
template<class T>
void MultiStack::push(T const &arg)
{
/* Push an object onto the stack */
}
 
template <class T, class... Args>
void MultiStack::emplace(Args&&... args)
{
/* Construct an object and push it onto the stack */
}
 
So now the only member function left to implement is "top". At first
glance, you might think that the way to implement "top" would be to have
multiple overloads of the member function "top", but C++ doesn't allow
function overloads that only differ by return type. The following is
disallowed:
 
class MultiStack {
public:
int top(void) { /* implementation */ }
float top(void) { /* implementation */ }
std::string top(void) { /* implementation */ }
};

Nor did I go with the following tactic of a template function to which
you would explicitly specify the type:
 
class MultiStack {
public:
template<class T>
T &top(void) { /* implementation */ }
};
 
int some_value = my_multistack.top<int>();
 
What C++ does allow though, is to throw an exception of any type -- and
so exception handling can be used as a more elaborate form of return
value.
 
And so I've written the "top" member function as a non-template function
to return void as follows:
 
void MultiStack::top(void)
{
/* Throw an exception from here in order
to return an object of any type */
}
 
In the event that a runtime error occurs in the "top" member function,
an exception of type "Stack::exception_top_error" will be thrown.
NOTE: It is permitted to push an "std::exception" (or any of its
derivatives such as "runtime_error") onto the stack -- and for this
reason "Stack::exception_top_error" does not derive from std::exception.
 
And so then a programmer could use the MultiStack class as follows.
First lets create an object of type 'MultiStack', and push (or emplace)
13 objects onto it:
 
MultiStack my_multistack;
 
my_multistack.push( int() );
my_multistack.push( double() );
my_multistack.push( int() );
my_multistack.push( std::pair<int,int>() );
my_multistack.push( int() );
my_multistack.push( int() );
my_multistack.push( float() );
my_multistack.push( int() );
my_multistack.push( double() );
my_multistack.push( std::runtime_error("Something went wrong!") );
my_multistack.push( std::pair<int,int>() );
my_multistack.emplace<int>(5);
my_multistack.emplace<std::string>(20, 'a');
 
Next let's run a loop to pop elements off the stack until it's empty:
 
for ( ; ! my_multistack.empty(); my_multistack.pop() )
{
try
{
my_multistack.top();
}
catch(int const &e)
{
cout << "Just got an int!" << endl;
}
catch(double const &e)
{
cout << "Just got a double!" << endl;
}
catch(std::pair<int,int> const &e)
{
cout << "Just got a pair<int,int>!" << endl;
}
catch(std::string const &str)
{
cout << "Just got a std::string, contents = '" << str << "'"
<< endl;
}
catch(std::exception const &e)
{
cout << "Just got a std::exception, what = '" << e.what() <<
"'" << endl;
}
catch(MultiStack::exception_top_error const &e)
{
/* something went wrong when popping */
cout << e.what() << endl;
}
catch(...)
{
cout << "Just got something unexpected! -- not necessarily
an error" << endl;
}
}

I have tested this and it's working as I intended it. I have a few more
things to tweak, such as the ability to stack objects that don't have
a copy-constructor, but I have the beef of the code written. See all my
code here:
 
#include <cassert> // assert
#include <cstddef> // size_t
#include <utility> // pair
#include <stack> // stack
#include <exception> // exception
#include <typeinfo> // typeid
#include <type_traits> // is_base_of, is_array
#include <string> // string
 
// Next 5 lines just for debugging
#include <stdexcept> // runtime_error
#include <cstring> // memcpy
#include <iostream>
using std::cout;
using std::endl;
 
class MultiStack {
public:
 
typedef std::size_t size_type;
 
MultiStack(void) = default;
 
MultiStack(MultiStack const &) = delete;
MultiStack(MultiStack &&) = delete;
MultiStack &operator=(MultiStack const &) = delete;
MultiStack &operator=(MultiStack&&) = delete;
 
class exception_top_error { /* important not to derive from
std::exception */
protected:
std::string str;
 
public:
exception_top_error(std::exception const &arg)
{
str = "ERROR: Exception thrown when calling
MultiStack::top(), type : '";
str += typeid(arg).name();
str += "', what() == '";
str += arg.what();
str += "'";
}
 
char const *what(void) const noexcept
{
return str.c_str();
}
};
 
protected:
 
template<class T>
static void Delete_Or_Throw(void *const arg_p, bool const
arg_to_throw_or_not_to_throw = true)
{
if ( ! arg_to_throw_or_not_to_throw )
{
if ( std::is_array<T>::value ) /* C++11 doesn't have 'if
constexpr' */
{
delete [] static_cast< typename
std::remove_extent<T>::type * >(arg_p);
}
else
{
delete static_cast<T*>(arg_p);
}
 
return;
}
 
// If control reaches here, we're not deleting -- instead we're
throwing
 
try
{
// Since the type T could be 'std::exception' or one of its
// derivations (e.g. 'std::runtime_error'), we need to make
// a distinction here when throwing, because the 'throw'
// command itself might throw an exception such as
// "bad_alloc", and so we throw an object of type:
// T[1u]
// instead of:
// T
throw *static_cast<T (*)[1u]>( static_cast<void*>(arg_p) );
}
catch(T const (&e)[1u])
{
//cout << "Got to Line " << __LINE__ << endl;
throw e[0u];
}
catch(std::exception const &e) /* To handle the likes of
"bad_alloc" */
{
//cout << "Got to Line " << __LINE__ << endl;
throw exception_top_error(e);
}
catch(...)
{
//cout << "Got to Line " << __LINE__ << " -- exception
thrown of unknown type (not derived from std::exception) -- calling
std::abort()" << endl;
std::abort();
}
}
 
std::stack< std::pair<void (*)(void*,bool), void*> > _stack;
 
public:
 
bool empty(void) const { return _stack.empty(); }
std::size_t size(void) const { return _stack.size (); }
 
void swap(MultiStack &rhs)
{
_stack.swap(rhs._stack);
}
 
void pop(void)
{
assert( ! _stack.empty() );
 
assert( nullptr != _stack.top().first );
assert( nullptr != _stack.top().second );
 
_stack.top().first( _stack.top().second, false /* just delete */
);
_stack.pop();
}
 
template<class T>
void push(T const &arg)
{
try
{
_stack.push( { &Delete_Or_Throw<T>, new T(arg) } ); //
This might throw bad_alloc
}
catch(std::exception const &e)
{
#ifndef NDEBUG
std::cout << "Exception thrown from " << __func__ << endl;

No comments: