FE 0.13.1
Header-only C++ frontend library
Loading...
Searching...
No Matches
cast.h
Go to the documentation of this file.
1#pragma once
2
3#include <cassert>
4#include <concepts>
5
6#include <format>
7#include <utility>
8
9#include <fe/assert.h>
10
11namespace fe {
12
13template<class T>
14concept Nodeable = requires(T n) {
15 T::Node;
16 n.node();
17};
18
19/// Like Nodeable, but for a class that spans a *set* of node kinds instead of a single one.
20/// Such a class declares `static constexpr bool isa_node(<node type>)` to decide membership.
21/// This keeps RuntimeCast::isa from falling back to a `dynamic_cast` for abstract bases that merely group nodes.
22template<class T>
23concept NodeSetable = requires(T n) {
24 { T::isa_node(n.node()) } -> std::convertible_to<bool>;
25};
26
27/// Inherit from this class using [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern),
28/// for some nice `dynamic_cast`-style wrappers.
29template<class B>
31public:
32 // clang-format off
33 /// `static_cast` with debug check.
34 template<class T> T* as() { assert(isa<T>()); return static_cast<T*>(this); }
35
36 /// `dynamic_cast`.
37 /// If @p T isa fe::Nodeable, it will use `node()`, otherwise a `dynamic_cast`.
38 template<class T>
39 T* isa() {
40 if constexpr (Nodeable<T>) {
41 return static_cast<B*>(this)->node() == T::Node ? static_cast<T*>(this) : nullptr;
42 } else if constexpr (NodeSetable<T>) {
43 return T::isa_node(static_cast<B*>(this)->node()) ? static_cast<T*>(this) : nullptr;
44 } else {
45 return dynamic_cast<T*>(static_cast<B*>(this));
46 }
47 }
48
49 template<class T> const T* as() const { return const_cast<RuntimeCast*>(this)->template as<T >(); } ///< `const` version.
50 template<class T> const T* isa() const { return const_cast<RuntimeCast*>(this)->template isa<T >(); } ///< `const` version.
51 // clang-format on
52
53 /// Like as() but - instead of merely asserting via isa() in `Debug` builds - throws a `std::logic_error` with a
54 /// formatted message when the cast fails.
55 /// @p fmt / @p args describe what was expected: a plain string works, as does a format string plus arguments.
56 /// If `B` is `std::formattable`, the offending object is appended to the message.
57 template<class T, class... Args>
58 T* expect(std::format_string<Args...> fmt, Args&&... args) {
59 if (auto res = isa<T>()) return res;
60 auto what = std::format(fmt, std::forward<Args>(args)...);
61 if constexpr (std::formattable<const B*, char>)
62 throwf("expected {}, but got '{}'", what, static_cast<const B*>(this));
63 else
64 throwf("expected {}", what);
65 }
66
67 /// `const` version.
68 template<class T, class... Args>
69 const T* expect(std::format_string<Args...> fmt, Args&&... args) const {
70 return const_cast<RuntimeCast*>(this)->template expect<T>(fmt, std::forward<Args>(args)...);
71 }
72};
73
74} // namespace fe
Inherit from this class using CRTP, for some nice dynamic_cast-style wrappers.
Definition cast.h:30
T * as()
static_cast with debug check.
Definition cast.h:34
T * isa()
dynamic_cast.
Definition cast.h:39
const T * expect(std::format_string< Args... > fmt, Args &&... args) const
const version.
Definition cast.h:69
const T * isa() const
const version.
Definition cast.h:50
T * expect(std::format_string< Args... > fmt, Args &&... args)
Like as() but - instead of merely asserting via isa() in Debug builds - throws a std::logic_error wit...
Definition cast.h:58
const T * as() const
const version.
Definition cast.h:49
Like Nodeable, but for a class that spans a set of node kinds instead of a single one.
Definition cast.h:23
Definition algo.h:17
void throwf(std::format_string< Args... > fmt, Args &&... args)
Throws a T (a std::logic_error by default) whose message is std::format(fmt, args....
Definition assert.h:15