简体   繁体   中英

#defined functions can only accept raw input not variables?

I was working on a small application that I'm building a simple CLI for. I wanted my CLI to have some colors because who likes boring old white and black consoles?? (jk no offense if you do:))

Then as I was building onto it I seem to have run into a problem that I, unfortunately, don't understand :(. The problem lies in some borrowed code that is supposed to help me clean the code by wrapping all the necessary code into neat little functions or definitions (you'll see what I'm talking about soon). I don't really know how definitions work in C++ or at least the more advanced ones but here is the code I currently have for wrapping the CLI color code functions into:

Colors.h

#ifndef _COLORS_
#define _COLORS_

/* FOREGROUND */
#define RST  "\x1B[0m"   // RESET
#define KRED  "\x1B[31m" // RED
#define KGRN  "\x1B[32m" // GREEN
#define KYEL  "\x1B[33m" // YELLOW
#define KBLU  "\x1B[34m" // BLUE
#define KMAG  "\x1B[35m" // MAGENTA
#define KCYN  "\x1B[36m" // CYAN
#define KWHT  "\x1B[37m" // WHITE

#define FRED(x) KRED x RST
#define FGRN(x) KGRN x RST
#define FYEL(x) KYEL x RST
#define FBLU(x) KBLU x RST
#define FMAG(x) KMAG x RST
#define FCYN(x) KCYN x RST
#define FWHT(x) KWHT x RST

#define BOLD(x) "\x1B[1m" x RST // BOLD
#define UNDL(x) "\x1B[4m" x RST // UNDERLINE


#endif  /* _COLORS_ */

So the problem is that this below works:

std::cout << FBLU("Hello, World. I'm blue!") << std::endl;

and this doesn't...

std::string randomString = "Hello, World. I'm blue!";
std::cout << FBLU(randomString) << std::endl;

Again, I'm not too familiar with how "defined functions" work but I was just wondering if anybody could possibly show me a new method that accepts both the raw text input and a variable input to the defined functions. Also if you could help me understand how definitions work more in C++ that would be awesome as well.

Macros work the same way as if you copy-pasted the macro definition to the place where it's used.

So this works:

std::cout << FBLU("Hello, World. I'm blue!") << std::endl;
// same as
std::cout << "\x1B[34m" "Hello, World. I'm blue!" "\x1B[0m" << std::endl;
// same as
std::cout << "\x1B[34mHello, World. I'm blue!\x1B[0m" << std::endl;

(C++ has a rule whereby adjacent string literals get joined together. I imagine this rule was added so that you can do exactly what you're doing here)

And this doesn't work:

std::string randomString = "Hello, World. I'm blue!";

std::cout << FBLU(randomString) << std::endl;
// same as
std::cout << "\x1B[34m" randomString "\x1B[0m" << std::endl;
// oops, syntax error

You are concatenating strings with macros, macros are not functions. You can't invoke macros with variables. For example the following program will print "hello world"

#include <iostream>

using std::cout;
using std::endl;

int main() {
    cout << "hello " "world" << endl;
}

See https://wandbox.org/permlink/EbG5ZUgVfKLo9lEq

So when you "invoke" the macro, after preprocessing your code looks like this

std::cout << FBLU("Hello, World. I'm blue!") << std::endl;
std::cout << "\x1B[34m" "Hello, World. I'm blue!" "\x1B[0m" << std::endl;

You are essentially combining the arguments to the macro together before compilation at preprocessing time. So with variables in your example you get the following

std::cout << FBLU(randomString) << std::endl;
std::cout << "\x1B[34m" randomString "\x1B[0m" << std::endl;

Which is ill formed C++, since you can't concatenate non string literals with string literals like that.


Remember that macros do nothing but plain text replacement.

The difference is because FBLU is a preprocessor macro (and NOT a function) that works as you expect ONLY if its parameter is a string literal. The preprocessor does TEXT substitution to produce source code - which is passed to a later phase of compilation.

The preprocessor will turn

FBLU("Hello, World. I'm blue!")

into

KBLU "Hello, World. I'm blue!" RST 

which (by substituting the macros KBLU and RST ) becomes

"\x1B[34m" "Hello, World. I'm blue!" "\x1B[0m"

which is a set of string literals, which get appended (again by the preprocessor) to become a single string literal

"\x1B[34mHello, World. I'm blue!\x1B[0m"

The net effect is that

std::cout << FBLU("Hello, World. I'm blue!") << std::endl;

is seen by the compiler as

std::cout << "\x1B[34mHello, World. I'm blue!\x1B[0m" << std::endl;

which is a perfectly valid code statement.

This doesn't work for

std::string randomString = "Hello, World. I'm blue!";
std::cout << FBLU(randomString) << std::endl;

since

FBLU(randomString)

is preprocessed to become

KBLU randomString RST 

which (by substituting the macros KBLU and RST ) becomes

"\x1B[34m" randomString "\x1B[0m"

Now, since randomString is an identifier (name of a variable, in this case), the preprocessor does no further macro substitution, and

std::cout << FBLU(randomString) << std::endl;

is seen by the compiler as

std::cout << "\x1B[34m" randomString "\x1B[0m" << std::endl;

which is not a valid statement.

The difference (depending on whether the argument to FBLU() is a string literal or a variable) is one of many reasons that usage of macros is actively discouraged in C++.

There are various alternatives that can be used instead, but the essential guideline is "Don't use macros".

For example, change the header files to convert the macros with no arguments into variable declarations, such as

#include <string>   // needed in the header since we're using std::string

/* FOREGROUND */
const std::string RST = "\x1B[0m";   // RESET
const std::string KRED = "\x1B[31m"; // RED

 // etc

and the macros with arguments into inline functions

inline std::string FRED(const std::string &x)
{
    return KRED + x + RST;
}

After doing this, both of your examples will work as expected.

The compilation of a C++ program involves three steps: preprocessing, compilation, linking.

So, you see, preprocessing goes first. At that time content of variable is not known to preprocessor, so your second code snippet unrolls to the following:

std::string randomString = "Hello, World. I'm blue!";
std::cout << "\x1B[34m" randomString "\x1B[0m" << std::endl;

which produces syntax error.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM