Program-defined metafunctions #797
Replies: 14 comments 39 replies
-
This is the result of the Result
//=== Cpp2 type declarations ====================================================
#include "cpp2util.h"
#line 3 "main.cpp2"
class empty;
template<typename T> class wrapper;
//=== Cpp2 type definitions and function declarations ===========================
#include <mutex>
#line 3 "main.cpp2"
class empty {
public: empty() = default;
public: empty(empty const&) = delete; /* No 'that' constructor, suppress copy */
public: auto operator=(empty const&) -> void = delete;
#line 3 "main.cpp2"
};
template<typename T> class wrapper {
private: T value;
};
static_assert(std::semiregular<wrapper<int>>);
static_assert(std::movable<wrapper<std::unique_ptr<int>>>);
static_assert(!std::copyable<wrapper<std::unique_ptr<int>>>);
static_assert(!std::movable<wrapper<std::mutex>>);
auto main() -> int;
//=== Cpp2 function definitions =================================================
#line 13 "main.cpp2"
auto main() -> int{}
Build output using GCC13 and
|
Beta Was this translation helpful? Give feedback.
-
Added @rule_of_zero
Generates template<typename T> class wrapper {
private: T value;
};
static_assert(std::semiregular<wrapper<int>>);
static_assert(std::movable<wrapper<std::unique_ptr<int>>>);
static_assert(!std::copyable<wrapper<std::unique_ptr<int>>>);
static_assert(!std::movable<wrapper<std::mutex>>); |
Beta Was this translation helpful? Give feedback.
-
Solved this by adding forward declarations of all headers up to This is the new build output:
|
Beta Was this translation helpful? Give feedback.
-
Here's another not-so-exciting metafunction,
And my refactor to use it.diff --git a/sources/jegp/utilities/utilities.cpp2 b/sources/jegp/utilities/utilities.cpp2
index 93a86c6..6cbc2fb 100644
--- a/sources/jegp/utilities/utilities.cpp2
+++ b/sources/jegp/utilities/utilities.cpp2
@@ -62,4 +62,13 @@ rule_of_zero: (inout t: cpp2::meta::type_declaration) = {
t.disable_member_function_generation();
}
+// `@trait<T>`, a `@struct` with a single member `this: T;`.
+// `export is_complex_number: @struct <T> type = { this: std::false_type; }` (takes 3 lines due to formatting), vs.
+// `export is_complex_number: @trait<std::false_type> <T> type = { }` (takes 1 line).
+export trait: (inout t: cpp2::meta::type_declaration) = {
+ t.require(t.get_members().size() == 0, "A trait must be an empty type.");
+ t.add_member("this: (t.get_argument(0))$;");
+ t.struct();
+}
+
} // namespace jegp
diff --git a/sources/jegp/numbers/numbers.cpp2 b/sources/jegp/numbers/numbers.cpp2
index 2e1e233..eca6e58 100644
--- a/sources/jegp/numbers/numbers.cpp2
+++ b/sources/jegp/numbers/numbers.cpp2
@@ -17,21 +17,11 @@ export number_scalar_t: <T> type == number_scalar<T>::type;
export is_inferred_number: <T> concept = std::regular<number_scalar_t<T>>;
-export is_number: @struct <T> type = {
- this: std::bool_constant<is_inferred_number<T>>;
-}
-export is_complex_number: @struct <T> type = {
- this: std::false_type;
-}
-export number_zero: @struct <T> type = {
- this: inferred_number_zero<T>;
-}
-export number_one: @struct <T> type = {
- this: inferred_number_one<T>;
-}
-export number_difference: @struct <T> type = {
- this: inferred_number_difference<T>;
-}
+export is_number: @trait<std::bool_constant<is_inferred_number<T>>> <T> type = { }
+export is_complex_number: @trait<std::false_type> <T> type = { }
+export number_zero: @trait<inferred_number_zero<T>> <T> type = { }
+export number_one: @trait<inferred_number_one<T>> <T> type = { }
+export number_difference: @trait<inferred_number_difference<T>> <T> type = { }
export number_scalar: @struct <T> type = { }
export is_number_v: <T> bool == base_characteristic_v<T, is_number, bool>;
@@ -172,35 +162,33 @@ export field_number_line: <T> concept = field_number<T> && number_line<T>;
export scalar_number: <T> concept = field_number<T> && (field_number_line<T> || is_complex_number_v<T>);
-// export vector_space: <T> concept =
-// negative<T> && multiplication_with<number_scalar_t<T>, T, T> && inverse<number_scalar_t<T>>;
-// clang-format off
-is_number: @struct <T> specialize<const T> type = { this: is_number<T>; }
-is_number: @struct <T, U> specialize<std::chrono::time_point<T, U>> type = { this: std::true_type; }
-is_number: @struct specialize<std::chrono::day> type = { this: std::true_type; }
-is_number: @struct specialize<std::chrono::month> type = { this: std::true_type; }
-is_number: @struct specialize<std::chrono::year> type = { this: std::true_type; }
-is_number: @struct specialize<std::chrono::weekday> type = { this: std::true_type; }
-is_number: @struct specialize<std::chrono::year_month> type = { this: std::true_type; }
+is_number: @trait<is_number<T>> <T> specialize<const T> type = { }
+is_number: @trait<std::true_type> <T, U> specialize<std::chrono::time_point<T, U>> type = { }
+is_number: @trait<std::true_type> specialize<std::chrono::day> type = { }
+is_number: @trait<std::true_type> specialize<std::chrono::month> type = { }
+is_number: @trait<std::true_type> specialize<std::chrono::year> type = { }
+is_number: @trait<std::true_type> specialize<std::chrono::weekday> type = { }
+is_number: @trait<std::true_type> specialize<std::chrono::year_month> type = { }
-is_complex_number: @struct <T> specialize<const T> type = { this: is_complex_number<T>; }
-is_complex_number: @struct <T> specialize<std::complex<T>> type = { this: std::true_type; }
+is_complex_number: @trait<is_complex_number<T>> <T> specialize<const T> type = { }
+is_complex_number: @trait<std::true_type> <T> specialize<std::complex<T>> type = { }
inferable_identities: <T> concept = std::common_with<T, number_difference_t<T>> && std::constructible_from<T, int>;
-number_zero: @struct <T> specialize<const T> type = { this: number_zero<T>; }
-number_one : @struct <T> specialize<const T> type = { this: number_one<T>; }
+number_zero: @trait<number_zero<T>> <T> specialize<const T> type = { }
+number_one: @trait<number_one<T>> <T> specialize<const T> type = { }
inferred_number_zero: @struct <T> specialize<T> type requires inferable_identities<T> = { value :== T(0); }
inferred_number_one: @struct <T> specialize<T> type requires inferable_identities<T> = { value :== T(1); }
-number_difference: @struct <T> specialize<const T> type = { this: number_difference<T>; }
+number_difference: @trait<number_difference<T>> <T> specialize<const T> type = { }
+// clang-format off
inferred_number_difference: @struct <T> specialize<T> type requires requires(c: T) { _ = c - c; } = {
this: std::type_identity<decltype(std::declval<const T>() - std::declval<const T>())>;
}
-number_scalar: @struct <T > specialize<const T> type = { this: number_scalar<T>; }
-number_scalar: @struct <T > specialize<T> type requires std::integral<T> = { this: std::type_identity<T>; }
-number_scalar: @struct <T > specialize<T> type requires std::floating_point<T> = { this: std::type_identity<T>; }
-number_scalar: @struct <T > specialize<std::complex<T>> type = { this: std::type_identity<T>; }
-number_scalar: @struct <T, U> specialize<std::chrono::duration<T, U>> type = { this: std::type_identity<T>; }
+number_scalar: @trait<number_scalar<T>> <T> specialize<const T> type = { }
+number_scalar: @trait<std::type_identity<T>> <T> specialize<T> type requires std::integral<T> = { }
+number_scalar: @trait<std::type_identity<T>> <T> specialize<T> type requires std::floating_point<T> = { }
+number_scalar: @trait<std::type_identity<T>> <T> specialize<std::complex<T>> type = { }
+number_scalar: @trait<std::type_identity<T>> <T, U> specialize<std::chrono::duration<T, U>> type = { }
// clang-format on
} // namespace jegp
diff --git a/sources/jegp/numbers/numbers.test.cpp2 b/sources/jegp/numbers/numbers.test.cpp2
index a98cd11..650b241 100644
--- a/sources/jegp/numbers/numbers.test.cpp2
+++ b/sources/jegp/numbers/numbers.test.cpp2
@@ -81,7 +81,7 @@ constexpr bool clang = false;
using namespace std::chrono_literals;
jegp: namespace = {
-is_number: @struct specialize<std::chrono::year_month_day> type = { this: std::true_type; }
+is_number: @trait<std::true_type> specialize<std::chrono::year_month_day> type = { }
} // namespace jegp
test_concepts: () = {
@@ -170,7 +170,7 @@ operator--: (inout x: number_line_post_decrement<1>, _: int) -> forward number_l
jegp: namespace = {
-is_number: @struct <Op: i8, Id: i32> specialize<::number_line<Op, Id>> type = { this: std::true_type; }
+is_number: @trait<std::true_type> <Op: i8, Id: i32> specialize<::number_line<Op, Id>> type = { }
} // namespace jegp
diff --git a/sources/jegp/quantities/quantities.cpp2 b/sources/jegp/quantities/quantities.cpp2
index 9519abf..7493720 100644
--- a/sources/jegp/quantities/quantities.cpp2
+++ b/sources/jegp/quantities/quantities.cpp2
@@ -19,8 +19,8 @@ template<class O, class U, class N> auto _cartesian_point2d(std::type_identity<s
} // namespace jegp
jegp: namespace = {
-// clang-format off
-is_number: @struct <O, Q, U, N> specialize<qty::point<O, Q, U, N>> type = { this: std::true_type; }
+
+is_number: @trait<std::true_type> <O, Q, U, N> specialize<qty::point<O, Q, U, N>> type = { }
number_zero: @struct <Q, U, N> specialize<qty::vector<Q, U, N>> type = {
value :== :qty::vector = (Q(), number_zero_v<N>, U());
@@ -29,8 +29,7 @@ number_one: @struct <Q, U, N> specialize<qty::vector<Q, U, N>> type = {
value :== :qty::vector = (Q(), number_one_v<N>, U());
}
-number_scalar: @struct <Q, U, N> specialize<qty::vector<Q, U, N>> type = { this: std::type_identity<N>; }
-// clang-format on
+number_scalar: @trait<std::type_identity<N>> <Q, U, N> specialize<qty::vector<Q, U, N>> type = { }
qty: namespace = {
diff --git a/sources/jegp/geometries/functions.cpp2 b/sources/jegp/geometries/functions.cpp2
index 32e608d..b56bfc2 100644
--- a/sources/jegp/geometries/functions.cpp2
+++ b/sources/jegp/geometries/functions.cpp2
@@ -33,24 +33,34 @@ geo: namespace = {
} // namespace geo
} // namespace jegp
-_: namespace = { }
-
-namespace boost::geometry::traits {
-
-template<class Number> struct tag<jegp::geo::vector2d<Number>> : std::type_identity<point_tag> { };
-template<class Number> struct dimension<jegp::geo::vector2d<Number>> : std::integral_constant<std::size_t, 2> { };
-template<class Number> struct coordinate_type<jegp::geo::vector2d<Number>> : std::type_identity<Number> { };
-template<class Number> struct coordinate_type<jegp::geo::vector2d<Number*>> : std::type_identity<Number> { };
-template<class Number> struct coordinate_system<jegp::geo::vector2d<Number>> : std::type_identity<cs::cartesian> { };
-template<class Number, std::size_t I> struct access<jegp::geo::vector2d<Number>, I> {
- static auto&& get(auto&& vector) {
- if constexpr (std::is_pointer_v<Number>) {
- return I == 0 ? *vector.x : *vector.y;
+boost: namespace = {
+geometry: namespace = {
+ traits: namespace = {
+
+ tag: @trait<std::type_identity<point_tag>> <N> specialize<jegp::geo::vector2d<N>> type = { }
+ dimension: @trait<std::integral_constant<std::size_t, 2>> <N> specialize<jegp::geo::vector2d<N>> type = { }
+ coordinate_type: @trait<std::type_identity<N>> <N> specialize<jegp::geo::vector2d<N>> type = { }
+ coordinate_type: @trait<std::type_identity<N>> <N> specialize<jegp::geo::vector2d<* N>> type = { }
+ coordinate_system: @trait<std::type_identity<cs::cartesian>> <N> specialize<jegp::geo::vector2d<N>> type = { }
+ access: @struct <N, I: std::size_t> specialize<jegp::geo::vector2d<N>, I> type = {
+ get: (forward vector) -> forward _ = {
+ if constexpr (std::is_pointer_v<N>) {
+ if (I == 0) {
+ return vector.x*;
+ } else {
+ return vector.y*;
+ }
} else {
- return I == 0 ? vector.x : vector.y;
+ if (I == 0) {
+ return vector.x;
+ } else {
+ return vector.y;
+ }
+ }
}
+ set: (forward vector, forward coordinate) vector.get() = coordinate;
}
- static void set(auto&& vector, auto&& coordinate) { get(vector) = coordinate; }
-};
-} // namespace boost::geometry::traits
+ } // namespace traits
+} // namespace geometry
+} // namespace boost |
Beta Was this translation helpful? Give feedback.
-
See #789 (reply in thread) for |
Beta Was this translation helpful? Give feedback.
-
Inspired by Flux, I'm working on Flux is similar to Here's how you use
That prints:
The example comes from c++17 - C++ range-v3 library: 'take'-ing first 3 perfect numbers works and halts; 'take'-ing first 4 doesn't stop after 4 - Stack Overflow. The idea to generate algorithms with metafunctions has a lot of potential.
Here's the examples I've written so far, and their comparison with
|
Beta Was this translation helpful? Give feedback.
-
When one writes a metafunction that fundamentally changes the way one writes code (
I actually think it's possible, and cppfront's Doing so feels paradoxical, which is why I 👍 d64dabf#r112614543.
before finally testing and committing. Playing with time like this gets worse if you're opening a PR and need to rebase.
YMMV. Those are the costs of wanting to use a metafunction in its own implementation. |
Beta Was this translation helpful? Give feedback.
-
Someone write a IIRC, hanickadot/compile-time-regular-expressions basically generates |
Beta Was this translation helpful? Give feedback.
-
Whew! I have not been able to absorb this, but I'm very intrigued. Cherry-picking some questions/points:
That's incomplete but I wanted to at least share some thoughts. Thanks for investigating this. |
Beta Was this translation helpful? Give feedback.
-
We have a set of related discussions/issues/PRs mentioned above, collecting them:
I appreciate all of these, and I'm wondering whether #907/#909 can provide a general mechanism to add "in-the-box" extensions like #904 into the cppfront repo? That is, take #904 and see whether, instead of as a special PR, it can be an exemplars of how general metafunction extensions work so that it's an example to follow for other metafunctions (and perhaps put out a call for other metafunctions to show the benefits of compile-time code generation)? Zoom callAs a next step, why don't we have a Zoom call to talk through this, perhaps next week... unless @JohelEGP or @MaxSagebaum needs more time to prepare the points below? Who. Everyone is welcome, but we should pick a time when at least @JohelEGP and @MaxSagebaum can make it. When. Would Tue Feb 13 at 10am U.S. Pacific time be convenient? Agenda. I think we want to cover:
How does that sound? |
Beta Was this translation helpful? Give feedback.
-
Following up: I don't mean to single out #904 either -- my ideal would be that all of I'd like the repo to continue to contain the metafunctions I wrote/provide/support with the compiler and that I think of as a 'standard library' of metafunctions (and so in a But I'd love to have the repo contain a bunch of other useful metafunctions, perhaps also in How does that sound? |
Beta Was this translation helpful? Give feedback.
-
For anyone who is interested in attending: We've confirmed tomorrow Tue Feb 13 at 10am U.S. Pacific time (time zones) to have an initial Zoom meeting to chat about this . If you'd like to attend, please email me and I'll send you the Zoom link. |
Beta Was this translation helpful? Give feedback.
-
#797 (reply in thread) Regarding last paragraph: I now understand why we need the flexibility of having the full power of Cpp1 at our disposal for meta and generation, and a interpreter would not be enough. I think evolving the current proposed implementation can help us achieve a successful solution for reflection in Cpp2. Something I just thought of and maybe we should have discussed is if there are plan to accommodate upcoming support of reflection from standard C++ or if its too early to tell (or if its completely orthogonal, if so, how). |
Beta Was this translation helpful? Give feedback.
-
Some notes from our Zoom meeting today: Cppfront one-step build. Check that we can still build cppfront the normal one-step way, when used for ordinary use including to use metafunctions provided with cppfront. PRs to add new metafunctions to the cppfront repo. These should add the new metafunctions in a new Users who do want to compile their own metafunctions. They should use this process: Write them in a separate
Future metafunction generation capabilities. We do (eventually) want metafunctions to be able to generate new declarations into existing scopes in the parse tree that are outside the type the metafunction is being applied to (see also #809), and into files via file I/O. For the latter:
C++26 reflection. It's too early to tell, but I think it will be complementary and additive. |
Beta Was this translation helpful? Give feedback.
-
I've built a proof of concept based on the idea at d8c1a50#commitcomment-131463103.
It's implemented with Boost.DDL.
CMakeLists.txt
Metafunctions need to go in a library.
And one can't be used in the same TU that defines it.
For #907:
CMakeLists.txt (v2)
Beta Was this translation helpful? Give feedback.
All reactions