3

Windows API を扱うときに、似ていない 2 ライナーを置き換えることで、コードの肥大化を抑えようとしていました。

TEMP t{0,1,2}; // let's say it's struct TEMP {int a; int b; int c}
SomeVeryVerboseFunctionName(&t);

ワンライナー付き

SomeVeryVerboseFunctionName(&TEMP{0,1,2});

しかし、エラーに遭遇しました:

式は、左辺値または関数指定子でなければなりません。

多くの試みの後、最終的にコンパイルするコードを思いつきました( MSVS 2013u4 ):

SomeVeryVerboseFunctionName(&(TEMP) TEMP{0,1,2});//explicit cast to the same type!

キャストが必要な理由をよりよく理解するために、簡単なテスト プロジェクトをセットアップしました。

#include <stdio.h>

struct A
{
    int a;
    int b;
    A(int _a, int _b) : a(_a), b(_b) {};
};

struct B
{
    int a;
    int b;
};

template <typename T> void fn(T* in)
{
    printf("a = %i, b = %i\n", in->a, in->b);
}

int main()
{
    fn(&A{ 1, 2 });      //OK, no extra magick
    /*  fn(&B {3, 4});      //error: expression must be an lvalue or function designator */
    fn(&(B)B{ 3, 4 });  //OK with explicit cast to B (but why?)
}

一部の構造体T に明示的なコンストラクターがある場合(A上記のコードのように)、ブレースで初期化された一時的な型のアドレスを取得Tし、それをポインターを取る関数に渡すことができることがわかりましたT*が、そうでない場合( のような)Bある場合、上記のエラーが発生し、 type への明示的なキャストによってのみ克服できますT

問題は、なぜ がBそのような奇妙なキャストを必要とし、Aそうでないのかということです。

アップデート

rvalue を lvalue として扱うことは MSVS の拡張機能/機能/バグであることが明らかになったので、実際に機能であるふりをしたい人はいますか (2010 年以降、MS がそれを維持するのに十分に使用されていますA) B。コンパイラを満足させるためにさまざまな方法で?AのコンストラクターとBのコンストラクターの欠如と関係があるに違いありません...

4

2 に答える 2

12

あなたがしていることは、実際には C++ では違法です。

Clang 3.5 は不平を言います:

23 : error: taking the address of a temporary object of type 'A' [-Waddress-of-temporary]
fn(&A {1, 2}); //OK, no extra magick
   ^~~~~~~~~

25 : error: taking the address of a temporary object of type 'B' [-Waddress-of-temporary]
fn(&(B) B {3, 4}); //OK with explicit cast to B (but why?)
   ^~~~~~~~~~~~~

のすべてのオペランドは&、一時的ではなく、左辺値でなければなりません。MSVC がこれらの構造を受け入れるという事実はバグです。上記の Shafik が指摘したリンクによると、 MSVC はこれらの左辺値を誤って作成しているようです。

于 2015-07-08T15:23:24.933 に答える
5
template<class T>
T& as_lvalue(T&& t){return t;}
// optional, blocks you being able to call as_lvalue on an lvalue:
template<class T>
void as_lvalue(T&)=delete;

合法的な C++ を使用して問題を解決します。

SomeVeryVerboseFunctionName(&as_lvalue(TEMP{0,1,2}));

ある意味でas_lvalueは逆moveです。と呼ぶこともできますがunmove、それは混乱を招きます。

C++ では、右辺値のアドレスを取得することは違法です。上記は右辺値を左辺値に変換し、その時点でアドレスの取得が有効になります。

右辺値のアドレスを取ることが違法である理由は、そのようなデータが破棄されることを意図しているためです。ポインターは、現在の行の終わりまでのみ有効です (左辺値のキャストによって右辺値が作成されていない場合)。このようなポインターは、特殊な場合にのみ有用です。ただし、Windows API の場合、そのような API の多くは、C スタイルのバージョン管理のためにデータ構造へのポインターを取ります。

そのために、これらはより安全かもしれません:

template<class T>
T const& as_lvalue(T&& t){return t;}
template<class T>
T& as_mutable_lvalue(T&& t){return t;}
// optional, blocks you being able to call as_lvalue on an lvalue:
template<class T>
void as_lvalue(T&)=delete;
template<class T>
void as_mutable_lvalue(T&)=delete;

正しい可能性が高いものはconstデータへの参照を返し (なぜ一時を変更するのですか?)、長いもの (したがって使用される可能性が低い) は非constバージョンを返すためです。

MSVC には古い「バグ」/「機能」があり、キャストの結果を含め、多くのものを左辺値として扱うべきではありません。/Zaその拡張機能を無効にするために使用します。これにより、他の方法では動作するコードがコンパイルに失敗する可能性があります。動作しているコードが動作せず、コンパイルが失敗する可能性さえあります。私はその逆を証明していません。

于 2015-07-08T17:38:35.143 に答える