I think it's worth separating compile-time and run-time metaprogramming. Smalltalk and Lisp did a lot with the latter to remove boilerplate. Higher-order abstractions that let you just write code for the things where you're deviating from some template are very easy in these languages. Python inherits this ability, but it seems to be used a bit less. JavaScript also inherits it and uses it even less.
Compile-time metaprogramming features are often less flexible but have two additional benefits: better performance (code specialisation at compile time, versus additional dynamic dispatch indirection at run time) and the opportunity to do better error reporting. The latter is the killer feature for me of C++ and Rust over C in systems programming: I can write APIs that will fail to compile if you use them wrongly. That's incredibly valuable, but it doesn't necessarily reduce the amount of code that you need to write.
Features like key-value coding and key-value observing in Objective-C didn't do anything at compile time but massively reduced the amount of code that you needed for GUI apps by allowing the frameworks to ship 100% generic controller classes that just needed to be parameterised on key names.