C ++マクロを関数のように動作させるにはどうすればよいですか?
-
03-07-2019 - |
質問
何らかの理由でマクロを記述する必要があるとしましょう: 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:これはコンパイルするべきではありません 。
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つの例をすべて渡すようにマクロを記述する方法はありますか?
注:受け入れられたヒントの共有方法が、より良い解決策があればここに投稿してください。私の方法よりも多くの票を得ることができます。 :)
解決
通常、マクロは避けてください。常にインライン関数を優先します。塩に値するコンパイラは、マクロのように小さな関数をインライン化できる必要があり、インライン関数は名前空間と他のスコープを尊重し、すべての引数を一度評価します。
マクロでなければならない場合は、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 ++)
が a
とをインクリメントするという事実ですb
を2回。
他のヒント
かなり賢い解決策があります:
#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つすべての例で期待どおりに動作します。
「マクロの機能を無視する」と言ったのは知っていますが、タイトルに基づいて検索することでこの質問を見つけることができるので、マクロを使用して関数をエミュレートするためのさらなるテクニックの議論が必要だと思います。
私が知っている最も近いのは:
#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回だけ評価します。これは、関数呼び出しの保証された機能です(どちらの場合も、これらの式のいずれにも例外がないと仮定します)。
- &quot; auto&quot;を使用して、あらゆるタイプに作用します。 C ++ 0xから。これはまだ標準C ++ではありませんが、単一評価ルールで必要なtmp変数を取得する他の方法はありません。
- 呼び出し元が名前空間stdから名前をインポートする必要はありませんが、元のマクロでは必要ですが、関数では必要ありません。
ただし、次の点で関数とは異なります:
- 一部の無効な使用では、さまざまなコンパイラエラーまたは警告が発生する場合があります。
- XまたはYに周囲のスコープからの 'MACRO_tmp_1'または 'MACRO_tmp_2'の使用が含まれている場合、エラーが発生します。
- 名前空間stdに関連:関数は独自の字句コンテキストを使用して名前を検索しますが、マクロは呼び出しサイトのコンテキストを使用します。この点で関数のように動作するマクロを記述する方法はありません。
- これは、void式(コンマソリューションなど)が使用できるvoid関数のreturn式として使用できません。これは、特に左辺値として使用する場合、目的の戻り値の型がvoidでない場合、さらに問題になります。しかし、コンマソリューションにはusing宣言を含めることができません。これらはステートメントであるため、1つを選択するか、({...})GNU拡張機能を使用してください。
これは libc6
からの回答です!
/usr/include/x86_64-linux-gnu/bits/byteswap.h
を見てみると、探していたトリックが見つかりました。
以前のソリューションに対するいくつかの批評家:
- Kipのソリューションでは、式の評価を許可していません。これは、最終的にはしばしば必要です。
- copproのソリューションでは、式が独立しているため、変数の割り当ては許可されませんが、式に評価できます。
- Steve JessopのソリューションはC ++ 11の
auto
キーワードを使用しますが、既知の/期待されるタイプを自由に使用してください。
トリックは、(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
マクロパラメーターは、(既に)括弧で囲まれ、予想されるタイプにキャストされています。
このソリューションは、パラメーターが一度だけ評価されるため、プリインクリメントとポストインクリメントで適切に機能します。
例として、要求されていない場合でも、 __ 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の最小値と最大値:
#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]
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
を含む)を返すことができる快適なスコープがあります。さらに、マクロパラメーターを複数回評価する問題が回避されます。
を使用してブロックを作成
#define MACRO(...) do { ... } while(false)
を追加しないでください。 while(false)の後に
あなたの答えは多重評価の問題に苦しんでいるので、(例えば)
macro( read_int(file1), read_int(file2) );
予期しない、おそらく望ましくないことを行います。
他の人が述べたように、可能な限りマクロを避けるべきです。マクロの引数が複数回評価された場合、副作用が存在すると危険です。引数のタイプがわかっている場合(または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
ifステートメントで常に中括弧を使用する方法を採用する場合は、
マクロには、最後のセミコロンが欠落しているだけです:
#define MACRO(X,Y) \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl
例1:(コンパイル)
if (x > y) {
MACRO(x, y);
}
do_something();
例2:(コンパイル)
if (x > y) {
MACRO(x, y);
} else {
MACRO(y - x, x - y);
}
例3:(コンパイルしない)
do_something();
MACRO(x, y)
do_something();