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

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/##fZDBDoMgEETvfsXculh7qP2hImJKSpUgSf17uiqJ1V...

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