Factorial macro example in C++23 metaprogramming,
#include <iostream>
consteval long factorial (int n) {
if (n == 0) return 1;
return n * factorial(n - 1);
}
int main() {
std::cout << factorial(7) << std::endl;
}
Exercise for the reader if using VC++ or clang/ninja, use import std instead.-- https://godbolt.org/z/TWe11hM6j
Nicely put 5040 in ESI register at compile time.
Granted, C++ isn't Lisp, but already has quite a room for creativity at compile time, and C++26 might finally have compile time reflection as well.
macro programming in Lisp would be "programming programs".
I don't see it here. This looks like compile-time execution to compute values. If it would be a macro, it could return source code.
Some people are calling this "multistage programming" [1] which is kind of related but not exactly the same as a macro system.
--
In general, a macro produces code at compile time, doesn't matter in which form it ends up in the binary, as long as the observable side effects are the same.
Example of a library that generates serialization code, https://github.com/getml/reflect-cpp
As mentioned the ongoing C++26 proposal with produce the desired source code at compile time, thus reducing the amount of code of libraries such that one.
Common misconception of non Lispers that macros are equivalent to compile time programming. You’re not simply moving the evaluation to compile time, you’re moving it upwards outside the program space into a higher dimension of programmability.
Not to dog on C++ unfairly, CTE is pretty neat after all.
Funnily enough, PGs “On Lisp” has some really neat macros in it that demonstrate capabilities that just can’t be replicated with template based macros, iirc.
"outside the program space into a higher dimension of programmability"
I can visualize this metaphor just fine, but I can't tell why it's useful. Can you make this concept more concrete?
I think an example would be helpful, conditionals:
Within the "program dimension" there is just no way to run code conditionally without an if, no matter how much you move left and right, you are constrained. It is only possible by using the "higher dimension".
This is different from constexpr if in C++? Where one branch will not exist in the compiled code?
(defmacro factorial (n)
(labels ((fact (m)
(if (= m 0)
1
(* m (fact (1- m))))))
`,(fact n)))
The `, has no use here and can be removed. Here the backquote and the evaluation just returns the computed value.Thus, this is okay:
(defmacro factorial (n)
(labels ((fact (m)
(if (= m 0)
1
(* m (fact (1- m))))))
(fact n)))
LABELS defines local recursive functions. The macro returns the result of calling FACT, which is a number and which is a valid form in Common Lisp. A number evaluates to itself. CL-USER > (macroexpand-1 '(factorial 10))
3628800
T
Agreed with the technical content and conclusion. However, I think it is worth pointing out that since C++11, it has had a mechanism to specify (maybe) compile-time computations that are written in plain C++: constexpr, https://en.cppreference.com/w/cpp/language/constexpr
(My parenthetical "maybe" is that I don't think compilers have to compute constexpr expressions at compile time. The compiler will be forced to when such expressions are used in contexts that require values at compile time. But I think it would be permissible for a compile to defer computation of a constexpr to runtime if the value isn't needed until runtime.)
Note that C++20 introduced consteval as a means to enforce compile time computation. See https://en.cppreference.com/w/cpp/language/consteval
Besides consteval and constinit, you can force evaluation with static constexpr.
Lisp macros can take arbitrary parameters and are written in lisp.
C++ macros can only take types and numbers (until variadic), and writing any code to operate on those inputs is challenging.
https://github.com/knome/metabrainfuck/blob/master/bf.cpp
It's not too bad :)
As a code golfer, this is beyond satisfying. Doing things in a few bytes is nice, yes, but doing them in a way no reasonable mortal ever would is even better.
There is the lisp meme “mom can we have defmacro? No we have defmacro at home”
C++ templates can also take other C++ templates (the template itself, not just an instantiation), enum values and I think in C++23 maybe even structs but I need to check
The post is actually about template metaprogramming. 'template macroprogramming' isn't really a thing.
Does this article have a (2002) that I missed or something?
Macaroni art versus the Mona Lisa.
I don’t have any experience with Lisp. But I think C++ templates and Rust macros are both super bad and impoverished compared to what can be done in Jai.
https://www.forrestthewoods.com/blog/using-jais-unique-and-p...
Except Jai is never going to get the use any of them have, so we use what is there.
That is not a coherent sentence.
I share it not to say “use Jai instead of C++/Rust”. But to instead say “templates and macros suck and it’d be great if Rust or other language copied good ideas into their ecosystem”.
The `#modify` thing looks pretty cool, but I can't help but think how a compiler/ide is supposed to analyze the body of such function and provide suggestions. Rust macros have a similar issue, but it's offsetted by the fact that generics are pretty powerful too and can be fully analyzed by the compiler.
Dudertiosa