What is the purpose of F() and FPSTR() in ESP8266WebServer -> FSBrowser? I found this code sample from the FSBrowser (Flash File System) example in the ESP8266WebServer library:
replyServerError(FPSTR(FS_INIT_ERROR));
replyBadRequest(F(&quot;DIR ARG MISSING&quot;));

I was a bit confused at what F() and FPSTR() do, so I looked up the source (WString.h) and found this:
#define FPSTR(pstr_pointer) (reinterpret_cast&lt;const __FlashStringHelper *&gt;(pstr_pointer))
#define F(string_literal) (FPSTR(PSTR(string_literal)))

Which raised more questions. How does this code work and what does this added complexity gain over just using char* or String as is?
EDIT: Added Metrics
Here's the memory usage before and after I removed all 25 instances of F() from a file system class, measured by PlatformIO Project Inspector:





With F()
Without F()
Difference




RAM Used
29,488 bytes (36.0%)
29,792 bytes (36.4%)
+304 bytes


Flash Used
355,312 bytes (34%)
355,188 bytes (34.0%)
-124 bytes


Total (uncompressed)
359,472 bytes
359,344 bytes
-128 bytes




Adding F() decreases RAM usage by 1% and increases Flash by around half that. That's for a single class with 25 instances of F(). For a program containing 20x this code, the difference can be significant. However, you'll run out of RAM before you get there. The space savings will be limited to around 5-8%. Still not nothing.
The purpose of the PSTR() macro is to configure the string to be used directly from flash memory. The string is then not loaded to dynamic memory at runtime as it would be without PROGMEM specifier set by the macro.
The purpose of the FPSTR() macro is to cast a string to 'dummy' FlashStringHelper type to help compiler choose the right overloaded function if dynamic memory and flash memory version of a function is available. An example would be the print function.
The F() macro combines this two macros. It makes the string a PROGMEM string and casts it to FlashStringHelper.