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_data
source 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