#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.
The C++ standard is consistent in how it uses "macro," and it's strictly about the preprocessor kind of macros. "Metaprogramming" is also used in the standard, though not as extensively or rigidly (only 8 times in N4950), but both in the specification and colloquially it's more than just slapping consteval on a function.
There is no reason to encode value computations using template expansion anymore. Even a lot of type level computation can be done via plain function calls and decltype. "Old school" recursive template installation is really only needed when you need specifically the capability to pattern match on types of template specialization.
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.
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.
--
[1]> (defmacro factorial (n)
(labels ((fact (m)
(if (= m 0)
1
(* m (fact (1- m)))))) `,(fact n)))
[2]> (factorial 1000)
402387260077093773543702433923003985719374864210714632543799910429938512398629020592044208486969404800479988610197196058631666872994808558901323829669944590997424504087073759918823627727188732519779505950995276120874975462497043601418278094646496291056393887437886487337119181045825783647849977012476632889835955735432513185323958463075557409114262417474349347553428646576611667797396668820291207379143853719588249808126867838374559731746136085379534524221586593201928090878297308431392844403281231558611036976801357304216168747609675871348312025478589320767169132448426236131412508780208000261683151027341827977704784635868170164365024153691398281264810213092761244896359928705114964975419909342221566832572080821333186116811553615836546984046708975602900950537616475847728421889679646244945160765353408198901385442487984959953319101723355556602139450399736280750137837615307127761926849034352625200015888535147331611702103968175921510907788019393178114194545257223865541461062892187960223838971476088506276862967146674697562911234082439208160153780889893964518263243671616762179168909779911903754031274622289988005195444414282012187361745992642956581746628302955570299024324153181617210465832036786906117260158783520751516284225540265170483304226143974286933061690897968482590125458327168226458066526769958652682272807075781391858178889652208164348344825993266043367660176999612831860788386150279465955131156552036093988180612138558600301435694527224206344631797460594682573103790084024432438465657245014402821885252470935190620929023136493273497565513958720559654228749774011413346962715422845862377387538230483865688976461927383814900140767310446640259899490222221765904339901886018566526485061799702356193897017860040811889729918311021171229845901641921068884387121855646124960798722908519296819372388642614839657382291123125024186649353143970137428531926649875337218940694281434118520158014123344828015051399694290153483077644569099073152433278288269864602789864321139083506217095002597389863554277196742822248757586765752344220207573630569498825087968928162753848863396909959826280956121450994871701244516461260379029309120889086942028510640182154399457156805941872748998094254742173582401063677404595741785160829230135358081840096996372524230560855903700624271243416909004153690105933983835777939410970027753472000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [3]>
works as expected.for the c++ version i don't think even `unsigned long long` can accommodate results from larger than approx. 21! or thereabouts.
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.
I can visualize this metaphor just fine, but I can't tell why it's useful. Can you make this concept more concrete?
The correct analogue for Lisp macros is not C++ templates, but the C preprocessor itself. Specifically, a Lisp macro gets to take a particular section of code and change it as it wishes, with everything already conveniently tokenized for the programmer's convenience. Imagine if you could just write your own C preprocessor as part of your program and have the compiler automatically execute it on specific program areas that want your preprocessing.
Rust macros work similarly to this, the main difference being your syntax needs to be tokenizable as Rust instead of Lisp. But they're also rather powerful. So, for example, in Rust you only have one object system which has structs, traits, and very limited higher-kindedness[0]. But there's plenty of other object systems Rust would like to interop with: Objective-C, Swift, COM, and C++ among others.
The canonical way of doing this in Rust is to write a macro[1] that takes your class definition and converts it into a series of structs, traits, and/or function pointers that suitably interop with the foreign code. Code outside the macro then can reference the class created by the system.
If you don't have macros, your other options are:
- Metaclasses, which are the canonical way in Python of doing foreign object interfaces, though with an added wrinkle: multiple inheritance from classes of different metaclasses requires writing a combined metaclass that does both. In macros you usually just can't mix them like that, though I doubt you'd need to define a single class accessible from, say, both Objective-C and Windows COM.
- Write your own damned preprocessor. This is what Qt did with MOC (metaobject compiler) to get signals and slots[2]. If C++ had macros, Trolltech probably would have written MOC as a macro instead of a separate build step with a separate C++ tokenizer.
[0] A concept which I will not be explaining in this post, but it has to do with things like generic associated types which were needed for lifetime bounds on async traits
[1] Usually a "procedural macro", which is different from the pattern-matching macros Rust usually teaches in ways that don't matter here
For example, imagine that you want to program with state machines and you need a short notation for that in your programming language. In Lisp you could design a syntax for a state machine and the Lisp macro would transform state machine descriptions into the code used to implement them -> the generated code typically will be longer and full of implementation details -> in the state machine description one would only specify what's necessary. The Lisp macro will do the code transformation from using the new control structure to the implementation of the control structure.
Thus one can view Lisp as a programmable programming language.
Think how much common code can exist in a software but cannot be refactored to a functions because it will have too many variables. Or the multiple problems with classes tree and overloading. Macros let you solve that.
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".
Being able to write macros means I can write code the SHAPE that I want, regardless of underlying implementation, but I can also manipulate other equivalently meta forms as well as primitives, which sets it at a higher level than templates.
I’m terrible at explaining, but if you’ve never tried lisp I strongly and wholeheartedly suggest you give it a try. For learning, I’d recommend Racket. Try and get at least as far as syntax-rules and syntax-case.
Anyways, sorry for the bad explanation!
(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(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.)
C++ macros can only take types and numbers (until variadic), and writing any code to operate on those inputs is challenging.
It's not too bad :)
HN discussion: https://news.ycombinator.com/item?id=31199992
https://www.forrestthewoods.com/blog/using-jais-unique-and-p...
Boost.Units is 20 years old. There is an unit system built in in c++11 just for duration, but shows what it can be done. There is no reason ever to use enable_if since C++20.
> Here's where Jai has a feature I've never seen before. I'm sure it exists in some language, but certainly not C++ or Rust!
The feature is #modify which is not needed in C++; for historical reasons, typically type level metaprogramming still different from value level, but it is not hard:
https://gcc.godbolt.org/z/PbzKeh38a
Boost.Hana adds a lot of the required infrastructure.