[HN Gopher] C++ template macroprogramming versus Lisp macros
       ___________________________________________________________________
        
       C++ template macroprogramming versus Lisp macros
        
       Author : oumua_don17
       Score  : 93 points
       Date   : 2024-11-15 19:47 UTC (1 days ago)
        
 (HTM) web link (simondobson.org)
 (TXT) w3m dump (simondobson.org)
        
       | liontwist wrote:
       | 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.
        
         | spacechild1 wrote:
         | The post is actually about template _meta_ programming.
         | 'template macroprogramming' isn't really a thing.
        
         | knome wrote:
         | https://github.com/knome/metabrainfuck/blob/master/bf.cpp
         | 
         | It's not too bad :)
        
           | Etheryte wrote:
           | 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.
        
           | liontwist wrote:
           | There is the lisp meme "mom can we have defmacro? No we have
           | defmacro at home"
        
         | dataangel wrote:
         | 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
        
           | jandrewrogers wrote:
           | C++20 added the ability to take class instances as non-type
           | parameters but the rules around the types of classes that can
           | be used in this way are pretty restrictive (for good reason).
        
         | binary132 wrote:
         | Yes, the purpose of C++ templates (not macros, macros are C
         | preprocessor) is to implement generic classes and functions,
         | not to implement arbitrary computation, and (iirc) it was only
         | discovered after their publication that they are in fact
         | Turing-complete.
        
       | scott_s wrote:
       | 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.)
        
         | spacechild1 wrote:
         | Note that C++20 introduced _consteval_ as a means to enforce
         | compile time computation. See
         | https://en.cppreference.com/w/cpp/language/consteval
        
         | pjmlp wrote:
         | Besides consteval and constinit, you can force evaluation with
         | static constexpr.
        
       | pjmlp wrote:
       | 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.
        
         | lispm wrote:
         | 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.
        
           | pjmlp wrote:
           | 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.
        
             | lispm wrote:
             | > In general, a macro produces code at compile time
             | 
             | The term "macro" is overloaded. In a typical Lisp they are
             | generating source as data, not just constant values. Lisp
             | macros also work in interpreters and they can use arbitrary
             | language features, incl. creating side effects or accessing
             | external data sources.
        
           | emmanueloga_ wrote:
           | Some people are calling this "multistage programming" [1]
           | which is kind of related but not exactly the same as a macro
           | system.
           | 
           | --
           | 
           | 1: https://en.wikipedia.org/wiki/Multi-stage_programming
        
         | cornstalks wrote:
         | The terms "macro" and "metaprogramming" already have well-
         | defined meanings in the C++ community. You're just hijacking
         | the terms and using them with some new definition.
         | 
         | 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.
        
           | pjmlp wrote:
           | Yeah, because using preprocessor macros would do the trick.
           | /s
        
             | foundry27 wrote:
             | It actually would! There are a handful of base-10 arbitrary
             | precision arithmetic implementations in the
             | preprocessor[1], and modern continuation-passing style
             | macro metaprogramming[2] makes it straightforward to write
             | a function like factorial once you get the syntax and
             | semantics down.
             | 
             | Libraries like Boost.Preprocessor can't be taken as an
             | example of "cutting edge" preprocessor metaprogramming. It
             | set a very low bar, and IMO has stifled a lot of developer
             | creativity.
             | 
             | [1] https://github.com/rofl0r/chaos-
             | pp/tree/master/chaos/preproc... [2]
             | https://github.com/rofl0r/order-
             | pp/blob/master/example/fibon...
        
               | gpderetta wrote:
               | I Remember Chaos and Order (20 years ago... now I feel
               | old).
               | 
               | The reason that they remained niche is that at the time
               | MSVC had an extremely non confirming preprocessor and
               | advanced PP libraries just weren't viable there.
               | 
               | Boos.PP was the most advanced you could get while staying
               | portable.
        
           | gpderetta wrote:
           | While I agree regarding the macro term, consteval very much
           | count as metaprogramming in C++.
           | 
           | 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.
        
         | binary132 wrote:
         | That is not a macro.
        
           | pjmlp wrote:
           | I doubt a #define would suffice.
        
         | signa11 wrote:
         | one fundamental difference being that clisp (i keep writing
         | this as 'clips', talk about lisp !) does not suffer from
         | precision issues, f.e. writing this                   [1]>
         | (defmacro factorial (n)                  (labels ((fact (m)
         | (if (= m 0)                          1                      (*
         | m (fact (1- m)))))) `,(fact n)))                   [2]>
         | (factorial 1000)
         | 
         | 402387260077093773543702433923003985719374864210714632543799910
         | 429938512398629020592044208486969404800479988610197196058631666
         | 872994808558901323829669944590997424504087073759918823627727188
         | 732519779505950995276120874975462497043601418278094646496291056
         | 393887437886487337119181045825783647849977012476632889835955735
         | 432513185323958463075557409114262417474349347553428646576611667
         | 797396668820291207379143853719588249808126867838374559731746136
         | 085379534524221586593201928090878297308431392844403281231558611
         | 036976801357304216168747609675871348312025478589320767169132448
         | 426236131412508780208000261683151027341827977704784635868170164
         | 365024153691398281264810213092761244896359928705114964975419909
         | 342221566832572080821333186116811553615836546984046708975602900
         | 950537616475847728421889679646244945160765353408198901385442487
         | 984959953319101723355556602139450399736280750137837615307127761
         | 926849034352625200015888535147331611702103968175921510907788019
         | 393178114194545257223865541461062892187960223838971476088506276
         | 862967146674697562911234082439208160153780889893964518263243671
         | 616762179168909779911903754031274622289988005195444414282012187
         | 361745992642956581746628302955570299024324153181617210465832036
         | 786906117260158783520751516284225540265170483304226143974286933
         | 061690897968482590125458327168226458066526769958652682272807075
         | 781391858178889652208164348344825993266043367660176999612831860
         | 788386150279465955131156552036093988180612138558600301435694527
         | 224206344631797460594682573103790084024432438465657245014402821
         | 885252470935190620929023136493273497565513958720559654228749774
         | 011413346962715422845862377387538230483865688976461927383814900
         | 140767310446640259899490222221765904339901886018566526485061799
         | 702356193897017860040811889729918311021171229845901641921068884
         | 387121855646124960798722908519296819372388642614839657382291123
         | 125024186649353143970137428531926649875337218940694281434118520
         | 158014123344828015051399694290153483077644569099073152433278288
         | 269864602789864321139083506217095002597389863554277196742822248
         | 757586765752344220207573630569498825087968928162753848863396909
         | 959826280956121450994871701244516461260379029309120889086942028
         | 510640182154399457156805941872748998094254742173582401063677404
         | 595741785160829230135358081840096996372524230560855903700624271
         | 243416909004153690105933983835777939410970027753472000000000000
         | 000000000000000000000000000000000000000000000000000000000000000
         | 000000000000000000000000000000000000000000000000000000000000000
         | 000000000000000000000000000000000000000000000000000000000000000
         | 000000000000000000000000000000000000000000000000
         | [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.
        
           | medo-bear wrote:
           | clisp is an unfortunately named specific implementation of
           | common lisp, like sbcl ecl clasp ccl abcl etc.
        
             | lispm wrote:
             | looks like he is using GNU CLISP.
        
           | pjmlp wrote:
           | That is when you would take advantage of C++ type system and
           | use a class implementing multi-precision numbers and operator
           | overloading.
        
             | medo-bear wrote:
             | Take advantage of, or be forced to use :)
        
             | signa11 wrote:
             | all at compile time ? now _that_ would really blow my hair
             | back :o)
        
               | kartoffelsaft wrote:
               | I believe a restriction for `constexpr`/`consteval` is
               | that you can do memory allocations but they must all be
               | freed at the end of the evaluation. Arbitrary precision
               | means arbitrary memory usage means allocation that's the
               | runtime's responsibility. Maybe there's a trick that gets
               | around that (define a type at compile time that's just
               | big enough for this specific number and return that?),
               | but I don't see it as possible without something weird.
        
               | gpderetta wrote:
               | You can allocate as much as you want, but the allocation
               | cannot survive to runtime time. So the final result would
               | have to be copied on a large enough static buffer.
        
           | fasa99 wrote:
           | It's always braggable to be able to do that more than another
           | language, but I'm not sure how mainstream or common it is to
           | have the use case of requiring numerical manipulation at the
           | scale of number of molecules in the universe
        
         | tuveson wrote:
         | For n<=8, GCC will also just inline it for the naive version in
         | C: https://godbolt.org/z/TvdcTvEKx
        
         | layer8 wrote:
         | That's neither a macro nor template metaprogramming.
        
       | forrestthewoods wrote:
       | 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...
        
         | SkiFire13 wrote:
         | 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.
        
         | pjmlp wrote:
         | Except Jai is never going to get the use any of them have, so
         | we use what is there.
        
           | forrestthewoods wrote:
           | 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".
        
             | pjmlp wrote:
             | First we need to be able to use Jai, so that those folks
             | can validate in practice how it works, not watching videos
             | about someone using their creation.
        
               | forrestthewoods wrote:
               | Someone has to lead by example. Jai is trying things no
               | one else is doing. No reason that Rust or other more
               | widely available regions can't lead.
               | 
               | Rust certainly led the charge with the borrow checker.
               | Why not do better than macros and templates and generics?
        
               | pjmlp wrote:
               | Like D, which predated it, and everyone can use, with
               | three available compilers.
        
       | lispm wrote:
       | (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
        
       | James_K wrote:
       | Macaroni art versus the Mona Lisa.
        
       | thunkingdeep wrote:
       | 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.
        
         | nightowl_games wrote:
         | "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?
        
           | eddd-ddde wrote:
           | 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".
        
             | jcul wrote:
             | This is different from constexpr if in C++? Where one
             | branch will not exist in the compiled code?
        
               | Jtsummers wrote:
               | Lisp macros take zero [0] or more unevaluated forms, does
               | something with them (which may be computing a value, see
               | the factorial example elsewhere in this discussion and
               | the submitted blog), and then returns a new form (which
               | could be that computed value, again see the factorial
               | example) which the Lisp system evaluates eventually.
               | 
               | One way to see this would be to do something like:
               | (defmacro print-value (a) (print a) a)       (print
               | (print-value 1))       (print (print-value (+ 1 2))
               | ;; output       1       1       (+ 1 2)       3
               | 
               | https://tio.run/##S87JLC74/18jJTUtNzG5KF@hoCgzr0S3LDGnNFV
               | BI1...
               | 
               | The value bound to a inside the macro is the unevaluated
               | form (the first and third items printed out). In this
               | case the macro itself is just the identity macro other
               | than its print effect so it returns the original form,
               | which is why we get 1 and 3 printed as well.
               | 
               | Here's an example of processing (very primitive) the
               | unevaluated form to produce something new:
               | (defmacro infix (a op b)         `(,op ,a ,b)) ;;
               | alternatively: (list op a b)       (print (infix 10 +
               | 20))       ;; output       30
               | 
               | Now we can get fancy (this is primitive still, but
               | works):
               | 
               | https://tio.run/##fZDBDoMgEETvfsXculh7qP2hImJKSpUgSf17uiq
               | J1V...                 (defmacro infix (expr)
               | (labels ((walk (expr)                        (cond
               | ((listp expr) `(,(second expr) ,(walk (first expr))
               | ,(walk (third expr))))                          (t
               | expr))))           (walk expr)))
               | 
               | This generates a valid (prefix notation) lisp form from a
               | more traditional infix form from mathematics. If I were
               | being more clever I wouldn't use the second item in the
               | list (the operation) directly but restrict it to valid
               | arithmetic operators and change how I walk the structure.
               | That would remove the need to force explicit parentheses
               | since I could add in a proper parsing step. This is a
               | very primitive version of what loop and other complex
               | macros do. They take essentially a different language,
               | parse it, and emit a valid Lisp form which is then
               | evaluated.
               | 
               | You could use this to get constexpr like behavior but
               | once you do that you run into problems, you can't do this
               | for example:                 (defmacro foo (a b) (+ a b))
               | (foo (+ 1 2) (+ 3 4)) ;; error       (let ((a 1) (b 2))
               | (foo a b)) ;; error       (foo 1 2) ;; => 3
               | 
               | It only partially works because it only works, when a and
               | b are both numbers. If they're symbols (second case) or
               | other forms (first case) then the macro attempts to
               | compute something that cannot be computed. You can fix
               | the first case by doing:                 (defmacro foo (a
               | b) (+ (eval a) (eval b))
               | 
               | But that still leaves the second case erroring out. You
               | could do something like what I did with infix which walks
               | the forms and determines if they can be evaluated (no
               | unbound variables) and then evaluate them conditionally,
               | leaving expressions with unbound symbols intact to be
               | processed later.
               | 
               | So C++ constexprs are less than Lisp macros, but if you
               | want to use Lisp macros to do the same thing as
               | constexprs you have to do more work. Check out _On Lisp_
               | and _Let Over Lambda_ for two books that go deep into
               | macros in Common Lisp.
               | 
               | [0] I honestly don't know why you'd want to do this, but
               | technically it's valid to do:                 (defmacro
               | foo () (some form to return))       (macroexpand '(foo))
               | ;; => (some form to return)
               | 
               | I cannot think of a case where this would be useful, but
               | someone else might think of one.
        
             | nightowl_games wrote:
             | "move left and right"? This meant nothing to me. 'higher
             | dimension' Still pretty abstract.
        
             | xmcqdpt2 wrote:
             | That's not that great of an example because you can do much
             | the same with closures, which are seamless in some
             | languages. In Scala for example,                   def
             | cond[T](p: Boolean, a: => T, b: => T): T = if (p) a else b
             | 
             | will only evaluate one of the two arguments.
             | 
             | https://docs.scala-lang.org/tour/by-name-parameters.html
             | 
             | You can do the same in Java with more syntax.
             | 
             | The real power of Lisp macro is that you introspect the
             | code and modify it, not that you can alter evaluation
             | eagerness.
        
           | skydhash wrote:
           | Let's say, you're writing a web application. In most
           | programming language you'd be using libraries or rely on a
           | framework. With Lisp macros, you can program the archetype of
           | a web application. And then use a simpler language to
           | describe you application. Think of it as programmable
           | snippets. Something like Ultisnips [0], but inherent to the
           | 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.
           | 
           | [0]: https://github.com/SirVer/ultisnips
        
             | nightowl_games wrote:
             | > cannot be refactored to a functions because it will have
             | too many variables.
             | 
             | I'm skeptical of this. Sounds ugly.
        
               | goatlover wrote:
               | Meaning create a domain specific language for that web
               | app.
        
             | ConspiracyFact wrote:
             | >Think how much common code can exist in a software but
             | cannot be refactored to a functions because it will have
             | too many variables.
             | 
             | Interesting...can you give an example of a macro that
             | solves this refactoring problem?
        
               | skydhash wrote:
               | One example is the ~use-package~ macro (Emacs plugin)
               | [0]. Using packages in emacs is mostly the same code over
               | and over. They've already been abstracted in functions,
               | but you still find yourself juggling with so many
               | utilities. You could write a bigger functions, but it
               | will then have a lot of conditional branches. This macro
               | selectively select the code it needs and transforming it
               | if needs be and then the result will be evaluated.
               | 
               | It's a bit hard to explain for me (English is not my
               | native language). But it's the difference between coding
               | a solution with all the edge cases baked in and coding an
               | archetype that let you add your own cases. With
               | functions, you abstract common algorithms, with macros
               | you abstract common architecture.
               | 
               | [0] https://github.com/jwiegley/use-
               | package/blob/a6e856418d2ebd0...
        
           | kmeisthax wrote:
           | This is probably not the whole picture, and I have a very
           | Rust-centric view of this, but I'll take a stab at it.
           | 
           | 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
           | 
           | [2] https://en.wikipedia.org/wiki/Signals_and_slots
        
           | stackghost wrote:
           | You can give the language new features, using the features
           | that the language provides.
        
           | lispm wrote:
           | it's "meta" programming, since Lisp macros do source
           | transformations: code to new code. So one writes code which
           | will rewrite code. For example some programming language
           | lacks a control structure one would want. Instead of waiting
           | for the _benevolent dictator for life_ implementing this
           | feature, one can do it by creating a macro, which implements
           | the control structure.
           | 
           | 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.
        
             | gpderetta wrote:
             | Templates are not nearly as powerful as lisp macros and
             | stitching together code is 100 time more complex (bordering
             | on Turing tarpit territory), but almost arbitrary compile
             | time code generation is still possible. See boost.lambda,
             | boost.spirit, eigen or anything using expression templates
             | for example.
             | 
             | Aside the complexity, the main thing C++ currently lacks is
             | code introspection to modify existing code without using an
             | ad-hoc DSL. I think that even the reflection proposals do
             | not go as far as actually introspecting code.
        
               | lispm wrote:
               | Can a template access a database/filesystem/network at
               | compile time? Any idea about that?
        
               | jandrewrogers wrote:
               | The proposals and features around std::embed and #embed
               | (part of C23) are on their way to enabling this.
        
           | thunkingdeep wrote:
           | Speaking frankly, I firmly believe it's all a matter of
           | taste. I like lisp because it matches the way I approach
           | problems. I like to think of the program as a jig I'm
           | building rather than a static component.
           | 
           | 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!
        
       | mgaunard wrote:
       | Does this article have a (2002) that I missed or something?
        
         | andyg_blog wrote:
         | I hate to agree. For an article about metaprogramming in C++,
         | you'd expect expert level C++ code, and not what's presented
         | here. It's bad. And the first example with the list even has a
         | bug.
        
       | mwkaufma wrote:
       | The "next" field in the lead code sample is supposed to be a
       | pointer, right?
        
       | rottc0dd wrote:
       | Another post comparing C and lisp Macros:
       | http://lists.warhead.org.uk/pipermail/iwe/2005-July/000130.h...
       | 
       | HN discussion: https://news.ycombinator.com/item?id=31199992
        
       | binary132 wrote:
       | It's clumsy because C++ templates are an implementation of
       | generic and dependent types, while C++ constexpr and consteval
       | functions are for arbitrary computation. The fact that template
       | metaprogramming _can_ be used for arbitrary computation is
       | actually an unhappy accident of its design that was only proven
       | after its publication.
        
       | anothername12 wrote:
       | Pffft, I'd just #. that factorial call and it'd be a normal
       | defun.
        
       | unnah wrote:
       | C++ templates do have the advantage that they can dispatch on
       | their argument types. Lisp macros are expanded at a purely
       | syntactic level, well before the compiler attempts to do any type
       | deduction, and you cannot provide different macro expansions for
       | different argument types without heroic efforts (which will most
       | likely be specific to a single Lisp compiler). Dispatching on
       | argument types is very useful in expression template based
       | metaprogramming, for example in the Eigen library for linear
       | algebra (https://eigen.tuxfamily.org/).
        
       ___________________________________________________________________
       (page generated 2024-11-16 23:01 UTC)