from Hacker News

C++ template macroprogramming versus Lisp macros

by oumua_don17 on 11/15/24, 7:47 PM with 71 comments

  • by pjmlp on 11/15/24, 9:22 PM

    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.

  • by thunkingdeep on 11/15/24, 10:55 PM

    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.

  • by lispm on 11/15/24, 10:21 PM

        (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
  • by scott_s on 11/15/24, 8:51 PM

    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.)

  • by liontwist on 11/15/24, 8:37 PM

    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.

  • by unnah on 11/16/24, 6:06 PM

    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/).
  • by rottc0dd on 11/16/24, 4:39 AM

  • by binary132 on 11/16/24, 5:09 AM

    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.
  • by James_K on 11/15/24, 10:52 PM

    Macaroni art versus the Mona Lisa.
  • by mgaunard on 11/15/24, 11:45 PM

    Does this article have a (2002) that I missed or something?
  • by forrestthewoods on 11/15/24, 9:42 PM

    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...

  • by mwkaufma on 11/16/24, 2:31 AM

    The "next" field in the lead code sample is supposed to be a pointer, right?
  • by anothername12 on 11/16/24, 7:50 AM

    Pffft, I’d just #. that factorial call and it’d be a normal defun.