Document number |
P3170R0 |
Date |
2024-02-29 |
Reply-to |
Jarrad J. Waterloo <descender76 at gmail dot com>
|
Audience |
Library Evolution Working Group (LEWG) |
sinkable exception error message
Table of contents
Abstract
Currently their is an implication of superfluous dynamic allocation associated with C++
exception handling when dealing with informative and dynamic error messages. This paper proposes that we allow programmers the ability to avoid this cost wherever possible.
Motivational Example
Following is a common example of the superfluous dynamic allocations presented in this paper.
#include <string>
#include <vector>
#include <iostream>
#include <source_location>
#include <sstream>
using namespace std::literals::string_literals;
template<class T>
class my_vector : public std::vector<T> {
private:
std::string to_string(const std::vector<T>::size_type pos, const std::source_location location)
{
std::stringstream ss;
ss << "file: "
<< location.file_name() << '('
<< location.line() << ':'
<< location.column() << ") `"
<< location.function_name() << "`: "
<< "invalid index: " << pos << ", size: " << this->size() << '\n';
return ss.str();
}
[[nodiscard]]
constexpr bool test( std::vector<T>::size_type pos ) const
{
return pos < this->size();
}
public:
using std::vector<T>::vector;
typedef std::vector<T>::value_type value_type;
typedef std::vector<T>::size_type size_type;
typedef std::vector<T>::reference reference;
typedef std::vector<T>::const_reference const_reference;
#if __STDC_HOSTED__ == 1
[[nodiscard]]
constexpr reference at( size_type pos, const std::source_location location = std::source_location::current() )
{
if(test(pos))
{
return (*this)[pos];
}
else
{
std::string temp = to_string(pos, location);
throw std::out_of_range(temp);
}
}
[[nodiscard]]
constexpr const_reference at( size_type pos, const std::source_location location = std::source_location::current() ) const
{
if(test(pos))
{
return (*this)[pos];
}
else
{
std::string temp = to_string(pos, location);
throw std::out_of_range(temp);
}
}
#endif
};
int main()
{
try
{
my_vector<int> myints{1};
int& pv = myints.at( 1 );
}
catch(const std::out_of_range& oor)
{
std::clog << oor.what() << '\n';
}
return 0;
}
In the my_vector
library author's code is a potentially superfluous dynamic allocation. This allocation is hidden from the library user who writes the main
function. This allocation is a consequence of the std::logic_error
and std::runtime_error
along with their derived classes copying a string. This superfluous allocation frequently occurs when the error message is not static due to the varying values of the exception's members or due to internationalization.
logic_error( const std::string& what_arg );
invalid_argument( const std::string& what_arg );
domain_error( const std::string& what_arg );
length_error( const std::string& what_arg );
out_of_range( const std::string& what_arg );
runtime_error( const std::string& what_arg );
range_error( const std::string& what_arg );
overflow_error( const std::string& what_arg );
underflow_error( const std::string& what_arg );
system_error( std::error_code ec, const std::string& what_arg );
format_error( const std::string& what_arg );
This allocation could be mitigated by both the library author with the addition of sinkable move based overloads to the standard library.
logic_error( std::string&& what_arg );
invalid_argument( std::string&& what_arg );
domain_error( std::string&& what_arg );
length_error( std::string&& what_arg );
out_of_range( std::string&& what_arg );
runtime_error( std::string&& what_arg );
range_error( std::string&& what_arg );
overflow_error( std::string&& what_arg );
underflow_error( std::string&& what_arg );
system_error( std::error_code ec, std::string&& what_arg );
format_error( std::string&& what_arg );
In the original example, the second superfluous allocation could be avoided with a sinking move.
std::string temp = to_string(pos, location);
throw std::out_of_range(temp);
|
std::string temp = to_string(pos, location);
throw std::out_of_range(std::move(temp));
throw std::out_of_range(to_string(pos, location));
|
Summary
With several extra overloads we can minimize dynamic allocations when performing exception handling. Instead of these superfluous allocations being hidden, they are placed in the control of exception throwers where it belongs.
Jarrad J. Waterloo <descender76 at gmail dot com>
sinkable exception error message
Table of contents
Abstract
Currently their is an implication of superfluous dynamic allocation associated with
C++
exception handling when dealing with informative and dynamic error messages. This paper proposes that we allow programmers the ability to avoid this cost wherever possible.Motivational Example
Following is a common example of the superfluous dynamic allocations presented in this paper.
In the
my_vector
library author's code is a potentially superfluous dynamic allocation. This allocation is hidden from the library user who writes themain
function. This allocation is a consequence of thestd::logic_error
andstd::runtime_error
along with their derived classes copying a string. This superfluous allocation frequently occurs when the error message is not static due to the varying values of the exception's members or due to internationalization.This allocation could be mitigated by both the library author with the addition of sinkable move based overloads to the standard library.
In the original example, the second superfluous allocation could be avoided with a sinking move.
Summary
With several extra overloads we can minimize dynamic allocations when performing exception handling. Instead of these superfluous allocations being hidden, they are placed in the control of exception throwers where it belongs.