简体   繁体   中英

C++ writing a proper function-like macro

I've been banging my head trying to write a proper #define function-like macro but am getting stuck. Here's the example I'm working with:

#include <iostream>
#define pMAKE(x,y,z,dest)\
    (dest).(x) = (x);\
    (dest).(y) = (y);\
    (dest).(z) = (z);

struct pt {
    double x, y, z;
};

int main() {
    pt p;
    pMAKE(0,1,2,p);
    return 0;
}

And the errors I'm getting:

A.cpp: In function ‘int main()’:
A.cpp:13: error: expected unqualified-id before ‘(’ token
A.cpp:13: error: expected unqualified-id before ‘(’ token
A.cpp:13: error: expected unqualified-id before ‘(’ token

What do the errors mean and why am I getting them? I managed to get the following to work the way I wanted, but I honestly just got lucky and I seriously don't understand what's going on.

#define pMAKE(X,Y,Z,dest)\
    dest.x = (X);\
    dest.y = (Y);\
    dest.z = (Z);

I appreciate every bit of help!

With the first version of your macro, pMAKE(0,1,2,p); expands to

p.0 = 0;
p.1 = 1;
p.2 = 2;;

In other words, you've pre-processed away references to the x , y , z members of pt by using x , y , z as both variable names (on the left of the assignments) and labels to be replaced by the pre-processor (on the right of the assignments).

(As noted by Konrad Rudolph) Your macro still has another serious error unfortunately. Consider code of the form

pt p;
if (foo)
    pMAKE(0,1,2,p);

which expands to

pt p;
if (foo)
    p.x = 0;
p.y = 1;  // happens regardless of 'foo' test
p.z = 2;; // happens regardless of 'foo' test

You can fix this (and get rid of that annoying extra semi-colon) by changing your macro to

#define pMAKE(X,Y,Z,dest)\
    do { \
        (dest).x = (X);\
        (dest).y = (Y);\
        (dest).z = (Z);\
    } while (0)
struct pt {
    double x, y, z;
    pt(double _x, double _y, double _z) : x(_x), y(_y), z(_z) { }
};

pt p (0.0, 1.0, 2.0);

Don't use macros in C++.

Or, as @potatoswatter suggested, get it done right at once in C++11

struct pt { double x, y, z; };
pt p = { 0.0, 1.0, 2.0 };

What do the errors mean and why am I getting them?

The macro replaces all occurrences of its parameters with the arguments; so

(dest).(x) = (x)

becomes

(dest).(0) = (0)

which is nonsense. The compiler expects a member name (an identifier, referred to as an "unqualified-id" by the language syntax), but sees (0) instead.

In the second version, x isn't a parameter name, so dest.x is not changed.

A better approach would be to avoid macros; use a function:

pt make_pt(double x, double y, double z) {
    pt p;
    p.x = x; 
    p.y = y;
    p.z = z;
    return p;
}

int main() {
    pt p = make_pt(0,1,2);
}

or just use aggregate initialisation:

int main() {
    pt p = {0,1,2};
}

and, in C++11, assignment from an initialiser list:

p = {4,5,6};

Macros work as textual replacement. Everywhere a parameter of the macro appears in the replacement list, is is textually replaced by the argument. So in your case, pMAKE(0, 1, 2, p); expands to:

(p).(0) = (0);
(p).(1) = (1);
(p).(2) = (2);;

(Actually, it would be all on one line, since macros don't preserve newlines, but I put it on three lines for clarity).

In your case, you don't want a macro. You want an inline function (probably a member function or constructor of pt ). Only use macros where you need their textual nature (such as combining tokens into identifiers etc.). For all else, use (inline) functions. Like this:

struct pt
{
  double x, y, z;
  pt(double ax, double ay, double az) : x(ax), y(ay), z(az) {}
};

int main()
{
  pt p(0, 1, 2);
}

Use an inline function,

pt& pMake(int x, int y, int z, pt&p){
    p.x=x;
    p.y=y;
    p.z=z;
    return p;
};

The .(x) member access accidentally uses the name of the parameter x . (Also, the second operand of a member access isn't an expression, so the parentheses don't belong.) Best practice is to keep all macro names and macro parameters in all caps, to prevent naming collisions. And avoid single character identifiers, too.

#define POINT_MAKE(X,Y,Z,DEST)\
    (DEST).x = (X);\
    (DEST).y = (Y);\
    (DEST).z = (Z);

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