任意値( 整数・何らかのポインタ )を、C++クラスのメンバ関数へのポインタ型に、型変換する方法はある?

[戻る]
ぶた 2013/02/28(木) 17:42:37
ご用件はタイトルの確認をさせて頂きたい事です。
自力調査の結論では×でした。下記コードの実験 と ネットでの情報調査 の結果から、
です。
が、自分の知識が及ばぬだけかもしれないと思い、皆様の知識を頼らせて頂こうと考えた
次第です。

struct c
{
    void f(){}
};

main()
{
    void ( c::*pMemberFunction )()( &c::f ); // ok. 当然できる
    pMemberFunction = 0;                     // ok. 当然 0( nullptr )はできる

    void*   p;
    pMemberFunction = reinterpret_cast< void ( c::* )() >( p );
                                             // ng. そんな変換、できん!

    pMemberFunction = reinterpret_cast< void ( c::* )() >( 1 );
                                             // ng. そんな変換、できん!

    pMemberFunction = (void ( c::* )())( 1 );// ng. キャスト書式を曖昧にしても、
できん物はできん!
}

【環境】
・VC++11 Pro. ( 部分的に C++11 )
・Win7 Pro. 64[bit]

上記コードが実効性のある( = 有益 )か否かは別にして、知識として知りたくてお尋ねさ
せて頂きました。

何卒宜しくお願い申し上げます。

tetrapod 2013/02/28(木) 21:09:02
有益かどうかをおいといて・・・っつことだと、
要するにできるかできないかの「根拠」がほしいってことかな?

C++ 言語仕様書 ISO/IEC 14882:1998 5.2.10 reinterpret_cast の9項
・メンバ関数へのポインタ右辺値は別のメンバ関数へのポインタに明示変換できる。
俺注釈:メンバ関数とは non-static メンバ関数のこと
・空メンバポインタ値は、目的の型の空メンバポインタ値に変換できる。

これ以外の規定は無いので「できない」(できてはならない)

実装面的技術論からいうと
・非メンバ関数(通常のC互換関数や static メンバ関数)へのポインタ
・static でないメンバ関数へのポインタ
は、大きさが違う。前者は int と同じ大きさにはならない。
なので、整数値からポインタへの変換は原理的に不可能(というか無意味)
http://www7b.biglobe.ne.jp/~robe/cpphtml/html03/cpp03058.html

tetrapod 2013/02/28(木) 21:14:58
あうっ痛恨の typo 前者でなくて後者。

>・非メンバ関数(通常のC互換関数や static メンバ関数)へのポインタ
は int (なり適切な大きさの整数型)1つの値と同じ大きさになるけど

> ・static でないメンバ関数へのポインタ
は時と場合により大きさが可変することがある。

なので、1個の整数値から後者を作ることは原理的に無意味であって
だから「できない」ように禁じられている。

ぶた 2013/02/28(木) 22:25:03
[[解決]]
どうも有難うございました。お蔭様で解決しました。嬉しいなぁ...

>要するにできるかできないかの「根拠」がほしいってことかな?
仰る通りです。

>C++ 言語仕様書 ISO/IEC 14882:1998 5.2.10 reinterpret_cast の9項
"プログラミング言語C++第3版"が、C++11仕様が固まって新版が出そうだから買い控えし
てたけど、安いから買います。そろそろ限界。

>・メンバ関数へのポインタ右辺値は別のメンバ関数へのポインタに明示変換できる。
>・空メンバポインタ値は、目的の型の空メンバポインタ値に変換できる。
>これ以外の規定は無いので「できない」(できてはならない)
>
>実装面的技術論からいうと
>・非メンバ関数(通常のC互換関数や static メンバ関数)へのポインタ
>・static でないメンバ関数へのポインタ
>は、大きさが違う。前者は int と同じ大きさにはならない。
>なので、整数値からポインタへの変換は原理的に不可能(というか無意味)
>http://www7b.biglobe.ne.jp/~robe/cpphtml/html03/cpp03058.html
なるほど。サンク機構を実現する条件として、
非静的メンバ関数へのポインタ型は 一般なポインタ型と異質なんですね。

だから、意図的に変換を許さない仕様になっている、と。

ご説明とURLの内容が、少なくとも私にとって とても分かり易かったです。
重ねて御礼を申し上げます。

tetrapod 2013/02/28(木) 23:21:28
えっと、言語仕様の国際規格が制定済みである今となっては
言語仕様はあくまで ISO/IEC (なり JIS なり ANSI なり) のものが正式だ。
いまや [プログラミング言語C++第n版] はあくまで参考書の位置づけにすぎないよ。

俺が引用したのも 国際規格書 の1項目。 [プロ言 C++] からではない。

ISO/IEC 14882 の最新は 2011 だけど、
市場のコンパイラのほとんどはまだ 2003 レベルなので
その辺は要注意。

ちなみに 5.2.10 の9項の続きにはかなり怖い文言が並んでいる。
メンバ関数へのポインタ右辺値を別の型に変換し、元の型に戻したら、元の値になる。
それ以外の結果は未規定。

未規定とは
・「正しいプログラム断片」であって
・その動作をコンパイラ・リンカなど言語処理系作者が文書化しなくてよい
ということ。

ある「メンバ関数へのポインタ」を別の「メンバ関数へのポインタ」に変換しただけ
(元の型に戻さない状態)で、使おうとすると
・そういうプログラムは正しいプログラムだが
・どう動作するかは不明
っつことだ。くわばらくわばら。

仲澤@失業者 2013/03/01(金) 09:57:17
tetrapodさんの発言が正しい対処方ですね。
一方、別の側面としてC/C++言語においては、

1.ある程度小さな量であれば任意の位置の情報を任意の位置に複写することは、
 まったく禁止されていない。

というのも事実です。
言語的、またOS上の意味をすべて排除すれば、なんでもありなわけですね。

YuO [E-Mail] 2013/03/01(金) 10:11:08
仕様上,
>     pMemberFunction = 0;                     // ok. 当然 0( nullptr )はできる
も「当然」ではなくて,4.11 Pointer to member conversionsの1段落で
> A null pointer constant (4.10) can be converted to a pointer to member type
とあるために0をメンバへのポインタ型に代入できます。

元々,(メンバへのポインタ型ではない) ポインタ型に0を代入できるのも,4.10 
Pointer conversionsの第1段落に
> A null pointer constant can be converted to a pointer type
とあるからですし,cv T *をvoid *に変換できるのも同第2段落に
> An rvalue of type “pointer to cv T,” where T is an object type, can be 
converted to an rvalue of type “pointer to cv void.”
とあるからです。
# 仕様は2003と2011両方から。


規格書自体は,INCITS/ISO/IEC 14882-2012 (ISO/IEC 14882:2011相当) ならANSIから$30
で買えます。
円建て支払いの場合,円安の影響で一時期よりは高くなりましたが,
ISO規格やJIS規格の価格に比べれば非常に安いので,買ってみてもよいのではないでしょ
うか。
http://webstore.ansi.org/RecordDetail.aspx?sku=INCITS%2fISO%2fIEC+14882-2012

ちなみに,JIS X 3014:2003はJSAにて冊子のみで16500円+税,
ISO/IEC 14882:2011はJSAでPDF/冊子ともに21420円+税,ANSI eStandards Store (PDF) 
で$285,ISO Store (PDF/PDF+CD) で238スイスフランです。
http://www.webstore.jsa.or.jp/webstore/Com/FlowControl.jsp?lang=jp&bunsyoId=JIS
+X+3014%3A2003&dantaiCd=JIS&status=1&pageNo=0

forty-five 2013/03/04(月) 20:17:16
メンバ関数のポインタが DWORD と同じ大きさであるとして無理やり変換するなら

template<class T>
inline T makeProcAddress(T proc, DWORD address)
{
  union
  {
    DWORD address;
    T proc;

  } x = { address };

  return x.proc;
}

pMemberFunction = makeProcAddress(pMemberFunction, 1);

tetrapod 2013/03/05(火) 09:37:41
意味ないの承知の上であえて無駄な変換するのであれば
T t=*reinterpret_cast<T*>(&p); // で十分。

gcc と vc++ で実装が違うし素直に virtual 使うほうがよさそう。

# x64 だとメンバ関数へのポインタは16バイト長か。胸熱だな。

ぶた 2013/03/06(水) 15:56:49
皆様、補足 有難うございました。

【tetrapod・YuOさん】
>いまや [プログラミング言語C++第n版] はあくまで参考書の位置づけにすぎない
そうなんですね。早合点せずに済みました。買うなら、YuOさんが紹介して下さったANSI
のHPを使う事にします。

>市場のコンパイラのほとんどはまだ 2003 レベルなのでその辺は要注意
そうですね。11への準拠度が高いと思えたコンパイラは、g++4.7.2ですね。VC++11は5割
という感じです。
http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
http://msdn.microsoft.com/ja-jp/library/hh567368(v=vs.110).aspx
http://blogs.msdn.com/b/vcblog/archive/2012/11/02/visual-c-c-11-and-the-future-
of-c.aspx を公開してくれたのは嬉しいですが...

【forty-fiveさん】
なるほど。DWORD も テンプレート型にすれば、更に汎化できますね。
が、どの環境でも変換動作が保障されるか確信できない( 特に仮想派生するクラスのメン
バ関数へのポインタを扱う時 )為、
採用できなさそうです。

古いスレッドには追加発言できません。