配列への型変換operatorの書き方

[戻る]
ホウジョウウサギ 2011/09/16(金) 18:06:44
文法的な事柄なのですが,

class CHoge
{
private:
    double m_Array[3];
public:
    typedef const double (&const_ARRAY3_ref)[3];
    operator const_ARRAY3_ref() const { return m_Array; }
};

このoperatorをtypedefを使わずに書くと
どのような記述になるのでしょうか?

AVAILABLE 2011/09/16(金) 22:23:36
operator double*()じゃあないでしょうか?

固定長配列では不可能だったような・

yoh2 2011/09/17(土) 03:27:05
素直に考えると

operator const double (&)[3]() const

かな? と思いましたが、やってみるとコンパイルが通りませんね。
気になったので、規格(JIS X 3014)を調べてみたところ、型変換関数 operator T の
Tの部分に書ける書式には制限があるようです。
具体的には、組み込み型、クラス名、typedefされた名前等の基本となる型名に、
cv修飾子、*、&をくっつけたもののみが許されるようです。
(詳細については、JIS X 3014 7. 宣言 や 12.3.2. 変換関数あたりを参照)

つまり、const double (&)[3]のように、()で囲まれた&や配列の[]を記述してはならない
ということのようです。
そのため、

> このoperatorをtypedefを使わずに書くと
> どのような記述になるのでしょうか?

については、typedefを使わずに書くことは不可能、となると思います。

# 実用的には、 AVAILABLE さんの書き込みのように、
# operator double *() や operator const double *() const で
# 事足りることがほとんどだと思いますが。

AVAILABLE 2011/09/20(火) 13:20:52
http://okwave.jp/qa/q6792452.htmlより一部改編


template<typename T>struct TYPE{
  typedef T Type;
};

class STRCONSTBUF{
  char buf[2];
public:
  operator TYPE<const char[2]>::Type&(){
    return buf + 0;
  }
};

ホウジョウウサギ 2011/09/20(火) 15:22:53
なるほど,素では書けないということですね.
勉強になりました.ありがとうござます.

>AVAILABLE様
ご提示の方法は
typedefを他のtemplateの中に移す方法,ということでしょうか.
これだと
operator const TYPE< double[3] >::Type &() const {    return m_Array;    }
のようにoperatorの定義個所に型が直接書いてあるから解り易くていいですね.



># 実用的には、 AVAILABLE さんの書き込みのように、
># operator double *() や operator const double *() const で
># 事足りることがほとんどだと思いますが。
確かに,
void f( const double (&P)[3] );    //要素数3の配列を渡してね
みたいな形の関数等はあまり見かけない(?)気がしますが,
こういう形だと何か不都合なこと等があったりするのでしょうか?

AVAILABLE 2011/09/20(火) 16:00:57
規格で無理となってるだけですので。。。
我々には解決できそうにないですね

AVAILABLE 2011/09/20(火) 16:01:57
まああえて言うなら、
double a[3];
double[3] a;
の問題ですね。

tetrapod 2011/09/20(火) 16:55:34
そもそも C の言語設計方針で「配列を直接引数に渡さない/渡せない」わけで、
これをプログラマ側に強制させるため C 言語仕様でも以下の2点を定めている。

1.関数の仮引数に配列を書くとポインタに読み替える
void test1(char a[4]); は void test1(char *a); と解釈される。

2.配列変数名は(一部の例外を除き)先頭の要素へのポインタと読み替える
void test2(void) {
 char a[16];
 test1(a); // ここの a は &a[0] と読み替える
}
// 例外のひとつが sizeof
// 上記例で sizeof(a) は 16 になる。 sizeof (char*) を得てはならない

C++ になって「参照」が作られた関係で言語仕様にいくつか追加されたが、
上記2つの規則は C との互換性を維持するため C++ でも有効。

(C++ のみ適用)
3.「参照」が初期化される場合には暗黙変換は行わない
4. typeid の引数は暗黙変換しない。

C++ で「配列への参照」を使うこと自体はまったく正しい。
ただ、その使用頻度はと問われると C で「配列へのポインタ」を使うのと同程度に
少ないのではないかな・・・

せっかく「配列への参照」を返却しても規則2によりほとんどの場合は
直ちに「配列の先頭要素へのポインタ」へ変換されてしまう。
その意味で [実用的には・・・] ということだと思うぞ。

ホウジョウウサギ 2011/09/20(火) 18:24:41
すみません,最初の質問内容と変わってしまうのですが,
実用性の話がどうにもよくわかりません…


-------------------------------
最初の書き込みのCHogeでの配列への参照を返すoperatorならば,

CHoge hoge;    //内部に配列を持っている人がいて,その配列を参照したいとき…
const double (&rArray)[3] = hoge;    //(1)配列への参照
const double *pArray = hoge;    //(2)配列先頭要素へのポインタ

と,(1),(2)両方で受け取ることが可能だが,
これが,配列への参照を返すoperatorではなくて,operator const double *() const
だと,(1)はダメで,(2)の形でしか受け取れない.
 ↓
void f( const double (&P)[3] );    //要素数3の配列を渡してね
なる形の関数引数に渡すためには,(2)ではダメで(1)である必要がある.
 ↓
あれ? だったら配列を返す側のoperatorを用意しておく方がとりあえず便利では?
-------------------------------


のように感じてしまうのですが……

そもそも
void f( const double (&P)[3] );
なんて形が用いられないから,ということなのでしょうか?
(だとすれば,さらにその理由は…??
 ・Cの時代から,配列先頭を指すポインタでやってきて,それで必要十分だから?
 ・引数の要素数を限定すると何か不都合があるから?
 ・わざわざ固定長にして要素数を限定する必要自体がまず無いから?
)

AVAILABLE 2011/09/20(火) 22:22:41
たぶん不必要だからでしょうか
あと、コンパイラ実装がめんどくなると思います

tetrapod 2011/09/21(水) 09:00:46
他言語ならいざ知らず C→C++ と来た人間にとっては
C 互換の [べた配列] は即時ポインタへ成り下がるのが言語仕様(既述2)なので
配列名=ポインタ右辺値であると身に染み付いているから、とか。

俺ならこんな例も挙げたい。 char array[100]; があるとき
func(array, 100); のようにポインタ+要素数を渡すように設計しておくと
func(array+len, 100-len-margine); のように
[途中から一部だけ引き渡す]ことも可能である。
そういう自由度が [要素数込み配列] 型引数だとなくなってしまう。

> CHoge hoge;    //内部に配列を持っている人がいて,その配列を参照したいとき…
> const double (&rArray)[3] = hoge;    //(1)配列への参照
これって要は臓物晒しでしょ。
・クラスのメンバ変数を外から任意のタイミングでかき回すことを許してしまう。
・CHoge が内部に double[3] を持っていることを、クラス外で知っておく必要がある
など、カプセル化原則に反しているんぢゃないかな。

任意長配列(≠普通の配列)や任意コンテナはクラスの形で実装するし、
その場合は臓物晒しせずにメンバ関数経由で取得設定すればよいわけで。

ホウジョウウサギ 2011/09/21(水) 10:26:34
いろいろとご教授いただきありがとうございます.

>[途中から一部だけ引き渡す]ことも可能である。
>そういう自由度が [要素数込み配列] 型引数だとなくなってしまう。
自分でもいろいろ試していて不便極まりないことに気がつきました.
この点だけでも,特に理由が無ければポインタ渡しの方が良さそうです.


>・CHoge が内部に double[3] を持っていることを、クラス外で知っておく必要がある
CHogeの例では,配列を持っていることもその参照をoperatorが返すことも,実装丸見え
なのでそんな感じですが,
普通は(?),
利用側には単に,戻り値の型(と,何かしら意味のある要素値が格納されているという情
報)が知らされているだけでは…
…あ,でも,それならoperatorを書くのではなく,そうとわかるような名前の
>メンバ関数経由で取得
するべき,ですね.


>・クラスのメンバ変数を外から任意のタイミングでかき回すことを許してしまう
これは,constを外された上で書きかえられる可能性がある,ということを指しているの
でしょうか?
(だとすれば,配列云々に限らず,内部のオブジェクトのconst参照を返すべきではな
い…ということでしょうか.)

tetrapod 2011/09/21(水) 12:50:43
> constを外された上で書きかえられる可能性がある
そこまでする奴は戦力外通知していいと思う。
#define private public とか・・・

> func(array+len, 100-len-margine); のように
自由度が高い=慎重に使わないと即バグる、わけで
あえて自由度を落として安全方向に振る、ってのも設計としてはありだと思うよ。
その辺はさじ加減次第かと。

> 内部のオブジェクトのconst参照を返すべきではない
複数のメンバ間での排他が必要な状況とかに対応するためにはメンバ関数が必要。
あるスレッドでは readonly であっても排他が必要なケースってのはある。
パフォーマンス上の問題があるとか積極的理由が無い限り メンバ変数の公開は、
やらないほうが安定。
# 参照で公開してもポインタで公開しても同じこと
# 積極的理由があるなら、ためらわずに公開すべし。

struct point_type { int x; int y; };
struct test_class { ...
 point_type p;
};
point_type const& rp=t.p; // あるいは point_type const* pp=&t.p;

あるスレッドで x と y を得ようとして次のようなコードを書いた。
x=rp.x;
<---> このタイミングで別スレッドが p を更新しちゃった
y=rp.y;
得られた x は旧値 y は新値でうまくないとか。

ロマ 2011/09/23(金) 09:37:22
ひっとしたら以下関係ありますか。

typedef double Array[3];

void func(Array array) // 値渡しのつもり
{
  array[0] = 2.0;
}

# 言語の話は苦手なので、外れていたらごめんなさい

ホウジョウウサギ 2011/09/27(火) 11:56:33
[[解決]]
最初の質問の答えが得られているので,[解決]を付けておきます.
返答頂いた皆様,ありがとうございました.


>tetrapod様
確かにマルチスレッドでは気をつけないと危険ですね.
# 排他処理は利用側で解決してね,という逃げはダメかな…

>ロマ様
「3要素の配列を渡してくれ」ということをコードで表現しつつも
固定長には強制はしない,といった感じでしょうか.
引数の話はこのくらいがちょうど良いかもしれませんね.

tetrapod 2011/09/27(火) 14:25:45
解決後だけど
> void func(Array array) // 値渡しのつもり
だけど、実はポインタ渡しとなってしまう (俺の提示仕様1に基づく)。

値渡しされた仮引数は呼び出し元の実引数のコピーなので、仮引数を書き換えても
呼び出し元実引数は変わらない、というつもりで array[0] = 2.0; すると、
提示コードは実はポインタ渡しであるがゆえに、呼び出し元の配列を壊している。
要注意!

struct array_wrapper {
 double array[3];
};
に対して void func(struct array_wrapper a) { ... } は真に値渡しとなる。

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