There are a few different ways of using code generation. They could be divided in three major groups:
- Generating code in a different language as output from a step in the compilation process. For the typical compiler this would be a lower-level language, but it could be to another high-level language as in the case of the languages which compile to JavaScript.
- Generating or transforming code in the source code language as a step in the compilation process. This is what macros does.
- Generating code in the source code language aswith a step separatelytool separately from the regular compilation process, where the generated. The output from this is code which lives as files together with the regular source code and is compiled along with it. For example entity classes for an ORM might be auto-generated from a database schema, or data transfer objects and service interfaces might be generated from an interface specification like a WSDL file for SOAP.
I would guess you are talking about the third kind of generated code, since this is the most controversial form. In the first two forms the generated code is an intermediate step which is very cleanly separated from the source code. But in the third form there is no formal separation between source code and generated code, except the generated code probably have a comment which say "don't edit this code". It stills opens the risk of developers editing the generated code which would be really ugly. From the viewpoint of the compiler, the generated code is source code.
Nevertheless, such forms of generated code can be really useful in a statically typed language. For example when integration with ORM entities, it is really useful to have strongly-typed wrappers for the database tables. Sure you could handle the integration dynamically at runtime, but you would lose type safety and tool support (code completion). A major benefit of statically type language is the support of the type system at the type of writing rather than just at runtime. (Conversely, this type of code generation is not very prevalent in dynamically typed languages, since in such a language it provides no benefit compared to runtime conversions.)
That is, if there is a code generator for something, then why not make that something a proper function which can receive the required parameters and do the right action that the "would generated" code would have done?
Because type safety and code completion are features you want at compile time (and while writing code in an IDE), but regular functions are only executed at runtime.
There might be a middle ground though: F# supports the concept of type providers which is basically strongly typed interfaces generated programmatically at compile time. This concept could probably replace many uses of code generation, and provide a cleaner separation of concerns.