| Slideware |
| Slideware |
| Personal opinions |
| Slideware |
| Personal opinions |
| No universal solution here |
| Slideware |
| Personal opinions |
| No universal solution here |
How might an average exception look like?
class bad_value : public std::exception
{
std::string message;
public:
explicit bad_value(const std::string& m)
: message(m) {}
const char* what() const noexcept override {
return message.c_str();
}
};class bad_value : public std::exception
{
public:
const char* what() const noexcept override {
return "bad_value";
}
};class bad_value : public std::runtime_error
{
using std::runtime_error::runtime_error ;
};using bad_value =
tagged_error<struct bad_value_tag> ;template<typename T>
class tagged_error : public std::runtime_error
{
using std::runtime_error::runtime_error ;
};template<typename T>
class tagged_error : public std::runtime_error
{
using std::runtime_error::runtime_error ;
};
using bad_value =
tagged_error<struct bad_value_tag> ;
using bad_index =
tagged_error<struct bad_index_tag> ;throw bad_value{"bad_value: the problem"};
Lets look at something different
template<int val>
struct Number {
};
using NumberOne = Number<1> ;
using NumberTwo = Number<2> ;void print(const NumberOne&) {
std::cout << "one\n" ;
}
void print(const NumberTwo&) {
std::cout << "two\n" ;
}
int main(){
print(NumberOne{});
print(NumberTwo{});
}template<typename T>
struct ErrorType {
constexpr ErrorType(T val) noexcept: value{val}
{};
T value ;
};
template<typename T, T val>
struct Error final: ErrorType<T> {
constexpr Error() noexcept: ErrorType<T>(val){};
};enum PosixErrNo {
/* NoError = 0, */
PosixEPERM = 1, PosixENOENT = 2, PosixESRCH = 3
};
using ErrEPERM = Error<PosixErrNo, PosixEPERM>;enum PosixErrNo {
/* NoError = 0, */
PosixEPERM = 1, PosixENOENT = 2, PosixESRCH = 3
};
using ErrEPERM = Error<PosixErrNo, PosixEPERM>;
// or, with some more syntax sugar
template<PosixErrNo V>
using PosixErrType = Error<PosixErrNo, V>;
using ErrENOENT = PosixErrType<PosixENOENT>;template<typename T>
void printErrorType (const ErrorType<T>& et) {
std::cout << et.value << std::endl ;
}
int main(){
ErrEPERM eperm{} ;
printErrorType(eperm) ;
constexpr ErrENOENT enoent ;
static_assert(enoent.value == 2, "");
}Numbers as type are sometimes useful
Numbers as type are sometimes useful
Not just for errors
Numbers as type are sometimes useful
Not just for errors
| Don’t wrap OS error codes! |
For OS error codes we have already
<system_error>
Often a log line like this is enough:
WhatHappend::File::Line::Function
C++20 will bring <source_location>
// untested since so far in no compiler
constexpr auto location =
std::source_location::current();
std::cout << location.file_name() << ":"
<< location.line() << " "
<< location.function_name()template<typename T>
struct typed_location {
static const char* type{typename(T)} ;
source_location location;
constexpr typed_location(source_location sl)
:location(sl)
};
using bad_value_exception =
typed_location<struct bad_value>;using bad_value_exception =
typed_location<struct bad_value>;
throw bad_value_exception {
std::source_location::current()
} ;
with a operator<< overload
produces exactly the log I want
bad_value::source.cpp::123::get_some_datasource location not here yet
compile-time typename does not exist at all
source location not here yet
compile-time typename does not exist at all
typename would be useful for a lot more,
e.g. serialization
Here is a possible implementation
Until we can use the one from the standard
| check your experimental folder |
struct source_loation{
const char* file;
int line;
const char* function;
constexpr source_loation(const char* fl,
int l,
const char* fn)
: file(fl), line(l), function(fn) {
}
} ;
#define current_location \
source_loation{__FILE__, __LINE__, __FUNCTION__}using cstr = const char* const ;
constexpr bool equal(cstr a, cstr b) {
return *a == *b &&
(*a == '\0' || equal(a + 1, b + 1));
}
int main() {
constexpr auto sl = current_location ;
static_assert (sl.line == 28) ;
static_assert (equal(sl.function, "main")) ;
}template <typename T>
constexpr auto type_name()
{
#if defined (_MSC_VER)
return __FUNCSIG__ ;
#elif defined (__GNUC__) || defined (__clang__)
return __PRETTY_FUNCTION__ ;
#else
error ("compiler not supported, add a PR")
#endif
}std::cout << type_name<struct Foo>();std::cout << type_name<struct Foo>();
MSVC
auto __cdecl type_name<struct Foo>(void)
gcc/clang
auto type_name() [T = Foo]The typename is there!

template<typename T>
constexpr auto type_name() {#if defined (_MSC_VER)
constexpr cstr thisname=__FUNCSIG__;
constexpr auto ang= first_c_in('<', thisname);
constexpr auto space= first_c_in(' ', ang);
constexpr auto tstart = (space == nullptr) ?
next_c(ang) : next_c(space) ;
constexpr auto tend=last_c_in('>', thisname);
#elif defined (__GNUC__) || defined (__clang__)
constexpr cstr thisname=__PRETTY_FUNCTION__;
constexpr auto eq= first_c_in('=', thisname);
constexpr auto past_eq= next_c(eq);
constexpr auto tstart=first_not(' ', past_eq);
constexpr auto tend=last_c_in(']', thisname);
#else
error ("compiler not supported, feel free to add a PR")
#endif constexpr auto len = tend - tstart ;
carrier<len+1> tnc{} ;
for (size_t i = 0 ; i < len; ++i){
tnc.name[i] = *(tstart + i) ;
}
return tnc; template<std::size_t S> struct carrier {
char name[S] = {0};
constexpr const char* str() const {
return &name[0];;
}
};struct err_location {
source_loation location ;
const char* error ;
constexpr err_location(source_loation sl,
const char* e)
: location{sl}, error{e} {}
};template<typename T>
struct typed_location : public err_location {
using name_t = decltype(sl::type_name<T>());
static constexpr name_t
tname = sl::type_name<T>();
constexpr typed_location(source_loation sl)
: err_location{sl, tname.str()} { }
};std::ostream& operator<<(std::ostream& os,
const err_location& e)
{
os << e.error << "::"
<< e.location.file << "::"
<< e.location.line << "::"
<< e.location.function;
return os;
}using bad_value =
typed_location<struct BadValue> ;
int main() {
constexpr bad_value err {current_location} ;
static_assert(sl::equal(err.error, "BadValue"),
"unexpected typename") ;
try {
throw bad_value {current_location} ;
} catch (const err_location& err) {
std::cout << err << std::endl ;
}
}Location soon in the standard
(output implementation defined)
Location soon in the standard
type_name via function macros suboptimal
Location soon in the standard
type_name via function macros suboptimal
other usage example
github.com/sharkdp/dbg-macro
Location soon in the standard
type_name via function macros suboptimal
other usage example
github.com/sharkdp/dbg-macro
'library' solution nameof
Neargye/nameof
Let’s hope for compile time reflections 2023
plus the time your project needs to add the required switches
:-(
What about the catch part?
No common base class
class my_type : public std::exception
{
public:
const char* what() const noexcept override {
return "my_type"; // + source location ....
}
};
std::exception seems not optimal,
the info does not fit into
const char* what() without copy compile time constants.
Also: C++ Core Guidelines
E.14: Use purpose-designed user-defined types as exceptions (not built-in types)
Reason A user-defined type is unlikely to clash with other people’s exceptions.
The best way to handle exceptions is to create a log entry and then quit.
struct connectionError{} ;
struct sqlError{} ;struct connectionError{} ;
struct sqlError{} ;
try {
throw sqlError() ;
} catch (...) {
log(std::current_exception()) ;
std::exit(1) ;
}void log(std::exception_ptr problem) {
assert(problem);
try {
std::rethrow_exception(problem);
} catch(const connectionError&) {
std::cout<< "connectioError \n" ;
} catch(const sqlError&) {
std::cout<< "sqlError \n" ;
}
}Sometimes std::error not the best base class
Sometimes std::error not the best base class
std::current_exception() and std::exception_ptr to put exception handling on one place
Sometimes std::error not the best base class
std::current_exception() and std::exception_ptr to put exception handling on one place
Wish for the future
Pattern matching, also for exception catching
plus review of scope handling
What will this print?
int main() {
std::cout << type_name<struct Foo>() ;
}
Foo
main::Foo
main()::Foo
something different
Implementation defined
Hopefully static reflections will fix this,
... if they will ever come
Now ….
I wanted to complain
that this works
using bad_value =
typed_location<'b','a','d',' ','v','a','l','u','e'>;
but this not
using bad_value =
typed_location<"bad value"> ;That we have to use macros
using bad_value =
typed_location<CTS("bad value")> ;To make this working
template<const char... Chars>
struct typed_location : public err_location {
static constexpr
const char text[sizeof...(Chars)]{Chars...} ;
constexpr typed_location(source_loation sl)
: err_location{sl, &text[0]} { }
};#define MAX_CONST_CHAR 100
#define MIN(a,b) (b)<(a)?(b):(a)
#define CTS(s)\
getChr(s,0),\
getChr(s,1),\
... continue up to \
getChr(s,99),\
getChr(s,100)
#define getChr(name, ii) ( \
(MIN(ii,MAX_CONST_CHAR)) \
<sizeof(name)/sizeof(*name)?name[ii]:0 \
)Or macro libraries like
using sad_value =
typed_location<CTS("sad value")> ;
using bad_value =
typed_location<CTS("bad value")> ;
void f(sad_value) {
printf("sad");
}
void f(bad_value) {
printf("bad");
}int main()
{
constexpr bad_value err{current_location} ;
static_assert(equal(err.error, "bad value"), "");
f(err) ;
f(sad_value{current_location}) ;
using bad_val2=typed_location<CTS("bad value")>;
using std::is_same_v ;
static_assert(is_same_v<bad_val2, bad_value>,"");
}That we have half solutions like this
template <char... chars>
using tstr = std::integer_sequence<char, chars...>;
template <typename T, T... chars>
constexpr tstr<chars...> operator""_tstr() {
return { };
}template <typename T>
struct typed_location;
template<char... Chars>
struct typed_location<tstr<Chars...>>
: public err_location {
static constexpr const char
text[sizeof...(Chars)+1]{Chars...,'\0'};
constexpr typed_location(source_loation sl)
: err_location{sl, &text[0]} { }
};using sad_value =
typed_location<decltype("sad value"_tstr)> ;
using bad_value =
typed_location<decltype("bad value"_tstr)> ;
void f(sad_value) {
printf("sad");
}
void f(bad_value) {
printf("bad");
}int main()
{
constexpr bad_value err{current_location} ;
static_assert(equal(err.error, "bad value"), "");
f(err) ;
f(sad_value{current_location}) ;
using bad_val2=
typed_location<decltype("bad value"_tstr)>;
using std::is_same_v ;
static_assert(is_same_v<bad_val2, bad_value>,"");
}only one small detail:
warning:
string literal operator templates
are a GNU extension
[-Wgnu-string-literal-operator-template]
constexpr tstr<chars...> operator""_tstr() {Not just numbers as types
All user defined types (with no private members)
Also char arrays !
template <std::size_t N>
struct wrapper {
char val[N];
constexpr wrapper(char const* src) :val{} {
for (std::size_t i = 0; i < N; ++i)
val[i] = src[i];
}
};
template <std::size_t N>
wrapper(char const(&)[N]) -> wrapper<N>;template <wrapper X> struct foo {
constexpr const char* str() {
return &X.val[0] ;
}
};
using f1 = foo<"hello">;
using f2 = foo<"bello">;
void fun(f1 f) { std::cout << f.str() ; }
void fun(f2 f) { std::cout << f.str() ; }int main()
{
static_assert(equal(f1{}.str(), "hello")) ;
static_assert(equal(f2{}.str(), "bello")) ;
fun(f1{});
fun(f2{});
}
Start thinking about boilerplate can lead to interesting journeys
Start thinking about boilerplate can lead to interesting journeys
some of them more useful than others
:-)
Generic programming
Compile time programming
New, upcoming and missing features
Some nice code games
Exceptions is a complex topic
as promised, no general solution
2 'official', 1 in-official
Exceptions
Error-codes
others (expected/outcome)
zero-overhead deterministic exceptions