web-dev-qa-db-ja.com

C ++マクロを関数のように動作させるにはどうすればよいですか?

何らかの理由でマクロMACRO(X,Y)を書く必要があるとしましょう。 (インライン関数を使用できない正当な理由があると仮定しましょう。)このマクロは、戻り値のない関数の呼び出しをエミュレートします。 。


例1:これは期待どおりに機能するはずです。

if (x > y)
  MACRO(x, y);
do_something();

例2:これはコンパイラエラーにならないはずです。

if (x > y)
  MACRO(x, y);
else
  MACRO(y - x, x - y);

例3:これはnotコンパイルする必要があります。

do_something();
MACRO(x, y)
do_something();

マクロを記述する単純な方法は次のとおりです。

#define MACRO(X,Y)                       \
cout << "1st arg is:" << (X) << endl;    \
cout << "2nd arg is:" << (Y) << endl;    \
cout << "Sum is:" << ((X)+(Y)) << endl;

これは3つの例すべてに失敗する非常に悪い解決策であり、理由を説明する必要はありません。

マクロが実際に行うことは無視してください、それはポイントではありません。


今、私が最もよく書かれているマクロを見る方法は、次のように中括弧で囲むことです:

#define MACRO(X,Y)                         \
{                                          \
  cout << "1st arg is:" << (X) << endl;    \
  cout << "2nd arg is:" << (Y) << endl;    \
  cout << "Sum is:" << ((X)+(Y)) << endl;  \
}

これは、マクロが1つのステートメントブロックにあるため、例1を解決します。ただし、マクロの呼び出しの後にセミコロンを挿入するため、例2は壊れています。これにより、コンパイラはセミコロンをそれ自体がステートメントであると見なします。つまり、elseステートメントはどのifステートメントにも対応しません。最後に、コードブロックにはセミコロンが必要ないため、例3はセミコロンがなくても問題なくコンパイルできます。


3つの例すべてに合格するようにマクロを記述する方法はありますか?


注: 受け入れられているヒントの共有方法 の一部として自分の回答を提出していますが、より良い解決策があれば、ここに投稿してください。私の方法よりも多くの票を得ることができます。 :)

46
Kip

通常、マクロは使用しないでください。常にインライン関数を優先します。その塩に値するコンパイラーは、マクロであるかのように小さな関数をインライン化できる必要があり、インライン関数は名前空間と他のスコープを尊重し、すべての引数を一度評価します。

マクロである必要がある場合は、whileループ(既に提案されている)が機能するか、コンマ演算子を試すことができます。

_#define MACRO(X,Y) \
 ( \
  (cout << "1st arg is:" << (X) << endl), \
  (cout << "2nd arg is:" << (Y) << endl), \
  (cout << "3rd arg is:" << ((X) + (Y)) << endl), \
  (void)0 \
 )
_

_(void)0_により、ステートメントはvoidタイプのいずれかに評価されます。セミコロンではなくコンマを使用すると、スタンドアロンとしてのみではなく、ステートメント内で使用できます。理由のホストのために、インライン関数をお勧めしますが、少なくともその理由はMACRO(a++, b++)abを2回インクリメントするという事実です。

40
coppro

かなり賢い解決策があります:

#define MACRO(X,Y)                         \
do {                                       \
  cout << "1st arg is:" << (X) << endl;    \
  cout << "2nd arg is:" << (Y) << endl;    \
  cout << "Sum is:" << ((X)+(Y)) << endl;  \
} while (0)

これで、単一のブロックレベルのステートメントができました。その後にセミコロンが必要です。これは、3つの例すべてで期待どおりに動作します。

43
Kip

「マクロの機能を無視する」と言ったのは知っていますが、タイトルに基づいて検索することでこの質問を見つけることができるので、マクロで関数をエミュレートするためのさらなるテクニックの議論が必要だと思います。

私が知っている最も近いものは:

#define MACRO(X,Y) \
do { \
    auto MACRO_tmp_1 = (X); \
    auto MACRO_tmp_2 = (Y); \
    using std::cout; \
    using std::endl; \
    cout << "1st arg is:" << (MACRO_tmp_1) << endl;    \
    cout << "2nd arg is:" << (MACRO_tmp_2) << endl;    \
    cout << "Sum is:" << (MACRO_tmp_1 + MACRO_tmp_2) << endl; \
} while(0)

これは次のことを行います。

  • 記載されている各コンテキストで正しく動作します。
  • 各引数を1回だけ評価します。これは、関数呼び出しの保証された機能です(どちらの場合も、これらの式のいずれにも例外がないと仮定します)。
  • C++ 0xの「auto」を使用して、任意の型に作用します。これはまだ標準C++ではありませんが、単一評価ルールで必要なtmp変数を取得する他の方法はありません。
  • 呼び出し元が名前空間stdから名前をインポートする必要はありません。これは元のマクロでは必要ですが、関数では必要ではありません。

ただし、次の点で関数とは異なります。

  • 一部の無効な使用では、異なるコンパイラエラーまたは警告が発生する場合があります。
  • XまたはYに、周囲のスコープからの「MACRO_tmp_1」または「MACRO_tmp_2」の使用が含まれている場合、問題が発生します。
  • 名前空間の標準に関連して、関数は独自のレキシカルコンテキストを使用して名前を検索しますが、マクロは呼び出しサイトのコンテキストを使用します。この点で関数のように動作するマクロを記述する方法はありません。
  • Void関数の戻り式として使用することはできません。これは、void式(コンマソリューションなど)で使用できます。これは、特に左辺値として使用する場合、目的の戻り値の型がvoidではない場合にさらに問題になります。ただし、コンマソリューションにはusing宣言を含めることができません。これはステートメントであるため、1つを選択するか、({...})GNU拡張機能を使用してください。
17
Steve Jessop

_libc6_からの回答は次のとおりです。 _/usr/include/x86_64-linux-gnu/bits/byteswap.h_を見てみると、あなたが探していたトリックが見つかりました。

以前のソリューションに対するいくつかの批評家:

  • Kipのソリューションでは、式の評価が許可されていません。
  • 式は独立しているため、copproのソリューションでは変数の割り当ては許可されませんが、式に評価できます。
  • Steve Jessopのソリューションは、C++ 11 autoキーワードを使用しますが、これは問題ありませんが、代わりに既知/予期されるタイプを使用してくださいです。

秘Theは、_(expr,expr)_構造と_{}_スコープの両方を使用することです:

_#define MACRO(X,Y) \
  ( \
    { \
      register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  )
_

registerキーワードの使用に注意してください。これはコンパイラーへのヒントにすぎません。 XおよびYマクロパラメーターは、(既に)括弧で囲まれ、castedで期待されるタイプになっています。このソリューションは、パラメータが一度だけ評価されるため、プリインクリメントとポストインクリメントで適切に機能します。

例として、要求されていなくても、___x + __y;_ステートメントを追加しました。これは、ブロック全体を正確な式として評価する方法です。

マクロが式に評価されないことを確認したい場合は、void();を使用する方が安全です。したがって、rvalueが期待される場所では不正です。

ただし、、解決策はISO C++準拠ではない _g++ -pedantic_:

_warning: ISO C++ forbids braced-groups within expressions [-pedantic]
_

_g++_に休息を与えるには、_(__extension__ OLD_WHOLE_MACRO_CONTENT_HERE)_を使用して、新しい定義を読み取ります。

_#define MACRO(X,Y) \
  (__extension__ ( \
    { \
      register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  ))
_

私のソリューションをさらに改善するために、Cの MINおよびMAXに見られるように、___typeof___キーワードを使用します

_#define MACRO(X,Y) \
  (__extension__ ( \
    { \
      __typeof__(X) __x = (X); \
      __typeof__(Y) __y = (Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  ))
_

これで、コンパイラは適切な型を決定します。これもgcc拡張です。

registerキーワードが削除されていることに注意してください。クラスタイプで使用すると、次の警告が表示されます。

_warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra]
_
12
ofavre

C++ 11はラムダをもたらしました。これはこの状況で非常に便利です。

#define MACRO(X,Y)                              \
    [&](x_, y_) {                               \
        cout << "1st arg is:" << x_ << endl;    \
        cout << "2nd arg is:" << y_ << endl;    \
        cout << "Sum is:" << (x_ + y_) << endl; \
    }((X), (Y))

マクロの生成力は維持しますが、必要なもの(voidを含む)を返すことができる快適なスコープがあります。さらに、マクロパラメーターを複数回評価する問題が回避されます。

6
Quentin

を使用してブロックを作成する

 #define MACRO(...) do { ... } while(false)

;を追加しないでください。 while(false)の後

4
Andrew Stein

あなたの答えは、複数評価の問題に苦しんでいるので、(例えば)

macro( read_int(file1), read_int(file2) );

予期しない、おそらく望ましくないことを行います。

2
Mike F

他の人が述べたように、可能な限りマクロを避けるべきです。マクロの引数が複数回評価された場合、副作用が存在すると危険です。引数のタイプがわかっている場合(またはC++ 0x auto機能を使用できる場合)、一時評価を使用して単一の評価を実施できます。

別の問題:複数の評価が発生する順序は、予想どおりではない場合があります!

次のコードを検討してください。

#include <iostream>
using namespace std;

int foo( int & i ) { return i *= 10; }
int bar( int & i ) { return i *= 100; }

#define BADMACRO( X, Y ) do { \
    cout << "X=" << (X) << ", Y=" << (Y) << ", X+Y=" << ((X)+(Y)) << endl; \
    } while (0)

#define MACRO( X, Y ) do { \
    int x = X; int y = Y; \
    cout << "X=" << x << ", Y=" << y << ", X+Y=" << ( x + y ) << endl; \
    } while (0)

int main() {
    int a = 1; int b = 1;
    BADMACRO( foo(a), bar(b) );
    a = 1; b = 1;
    MACRO( foo(a), bar(b) );
    return 0;
}

そして、それはコンパイルされて私のマシン上で実行される出力です:

 X = 100、Y = 10000、X + Y = 110 
 X = 10、Y = 100、X + Y = 110 
0
jwfearn