質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

意見交換

クローズ

10回答

1546閲覧

座標系を制限した型を考えたのですが自分でも「コレどうなの?」ってなる

fana

総合スコア11663

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

3クリップ

投稿2023/07/03 06:28

編集2023/07/03 06:30

0

3

いくつかの「XXX座標系」という概念がある状況での話です.
例えば,3次元のグラフィクスを扱うような場面では,よく{ワールド座標系,カメラ座標系}といった概念が出てきますよね.そういう話です.
(以下,コード例では簡便さのため2次元で書きますが)

例えば

C++

1// 2//行列やベクトルの型とか演算は残念なことに C++ 標準で用意されていないから 3//自前でこんなのを書いて(あるいは既存の何かを持ってきて)使う 4// 5class Vec2{ ... }; //2次元のベクトル用の型 6class Mat2x2{ ... }; //2x2 の行列の型 7 8// 9int main() 10{ 11 //なんかベクトルと行列を用いた演算 12 Vec2 V{ 10, 20 }; 13 Mat2x2 TransMat{ {0,1}, {-1,0} }; 14 Vec2 TransedVec = TransMat * V; //(こういう operator の実装もあるのだとして) 15}

みたいなコードがあるとして,この main() 内のコードは
「何らかの座標変換をしているんじゃないかな感」はある(と思う)のですが,
「どの座標系の量をどの座標系の量に変換しているのか」は全くわかりません.
それ以前にベクトル V とはどの座標系での量として定義している つもり/ハズ なのか? といったことも謎です.

で,私くらいの馬〇になると,これ系の演算を実装するときに
その辺の事柄を取り違えたり間違ったりしてしまうことが割とあって,その結果は(当たり前ですが)全く意味不明な演算結果になります.
そういう場合のデバッグは結構厄介です.
(特に「分かり切っている」つもりの箇所でやらかしてたりすると気づきにくい)


そこで,↑の Vec2 とか Mat2x2 とかいう型を
「ベクトルです」「マトリクスです」という単純な(汎用な)存在とするのではなくて,
「ワールド座標系でのベクトルです」とか「ワールド座標系からカメラ座標系への変換行列です」とかいう専用の型にしてしまえば
間違いを事前抑止できる可能性があるのではなかろうか? とか夢想したわけです.

扱う座標系の種類の個数が定まっているのであれば,

C++

1//座標系の種類 2enum class CoordinateSystem{ World, Camera }; 3 4//特定の座標系におけるベクトル 5template< CoordinateSystem CS > 6class Vec2 7{ 8public: 9 //各種演算は同じ座標系のベクトル同士じゃないとできない 10 Vec2 &operator +=( const Vec2 &rhs ); 11 Vec2 &operator -=( const Vec2 &rhs ); 12 double Dot( const Vec2 &rhs ) const; 13}; 14 15//From座標系の量(ベクトル)を To座標系での量に変換するマトリクス 16template< CoordinateSystem From, CoordinateSystem To > 17class Mat2x2 18{ 19public: 20 //乗じるベクトルは From座標系で,乗じた結果は To座標系 21 Vec2<To> operator *( const Vec2<From> &V ) const; 22}; 23 24//型名が長ったらしいので using 25using WorldVec = Vec2< CoordinateSystem::World >; 26using CameraVec = Vec2< CoordinateSystem::Camera >; 27using W2CMat = Mat2x2< CoordinateSystem::World, CoordinateSystem::Camera >;

とかしてしまえば,
変数を 宣言/定義 する際には必ず座標系を明確に意識せねばならず,馬鹿みたいな間違いを抑制できるのではないかな,と.
また,

C++

1WorldVec V1{ 1,2 }; 2CameraVec V2{ 3,4 }; 3auto V3 = V1 + V2; //←その演算の意味は何なの?

みたいなのがコンパイルを通らなくなるのは大変素敵なのではなかろうか? と.

……が,他方では
「理由ははっきりしないが,なんだか馬鹿馬鹿しい(仰々しい?)コードと思える」
「そんなの変数名とかで表現すれば十分じゃね? 不足ならコメントでも書けと」
みたいな気もしてきます.

↑のようなコード,どう思いますか?
(「意見交換」ならこんな雑な聞き方をしても良いと思っている)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

回答10

#1

can110

総合スコア38266

投稿2023/07/03 07:18

「ベクトルです」「マトリクスです」という単純な(汎用な)存在とするのではなくて,
「ワールド座標系でのベクトルです」とか「ワールド座標系からカメラ座標系への変換行列です」とかいう専用の型にしてしまえば

非常に限定的な世界前提ならともかくとして、とにかく「止めろ」といいます。というか止めてください。
座標やベクトルは相対的なモノなので、それを意識しないでよいような仕組みはその外側に持たせるべきです。
(ある程度経験のある相手になら、こんな雑な回答しても良いと思っています)

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#2

fana

総合スコア11663

投稿2023/07/03 07:30

編集2023/07/03 07:54

#1

外側に持たせる

の具体的なイメージがわきません.
簡単に例示などいただけると助かります.(経験のない初心者ですみません)


(質問文にもなんとなく書いてはいるのですが)
「おいやめろ」という感覚は自分の中にもあります.
なんだろう…それがはっきりしないというか,完全に100%に達しなくてもやもやしているというか…?

(例えるなら自転車の補助輪みたいな雰囲気を感じる.
ふらついているなら役にも立つだろうが,「自転車でどっか行こうぜ」って時にそれ付けてくるんじゃねぇよw みたいな…??)


全然関係ないですが,「意見交換」だと「回答依頼」機能が無いんですね.

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#3

fana

総合スコア11663

投稿2023/07/03 09:50

編集2023/07/03 09:57

うーん,他の意見交換と比べて全然意見が来ないのは話がイマイチ漠然としているからなのかな?

…というわけで,具体的な失敗例について述べてみます.
どの程度の馬〇な失敗話を想定して本件を考えるに至ったのか,というのがわかるように.

この私の回答の中ごろに「カメラの球面上での移動」という段落がある.
なんやかんや書かれているが,要は【カメラの姿勢を「カメラ座標系でのベクトルとして示された」回転軸周りに回転させる】という話.
この話ではカメラ姿勢は CX, CZ というカメラ座標系の基底ベクトルで保持しているから,やることはこれらの値を更新すること.

で,コレを実装したときに,なんと
CX = R * CXCZ側も同様)
とか書いてその動作結果を見てしばらく「???」ってなってたわけです.
どう見ても〇鹿です.ほんとうにありがとうございました.自分で自分にびっくりした!

……で,もしも本件のような変な実装を採用したならば,この R * CX がコンパイルエラーになる想定.
なぜならば,ここで回転行列 R の型は本件のコード例に合わせて書けば Matrix3x3<カメラ座標,カメラ座標> であり,他方,CXVec3<ワールド座標> となるから.
「そんなコンパイルエラーが出る状況までコードを書き進めている時点でいろいろとヤバい」という話は置いといて,ここでコンパイルエラーが出るならば,何を間違ったのかを気づけない余計な時間を過ごさずに済むんじゃないかなぁ,と.
(あるいはそれよりも前に R の変数定義を書いた時点で,これから R * CX とか書こうとしている自分の過ちに気づけるハズ)

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#4

SaitoAtsushi

総合スコア5466

投稿2023/07/04 06:04

型を分ける甲斐があるかどうかはコードの規模などにもよるので諸々を加味して総合的な判断が必要であり積極的にやるべきともやめたほうが良いとも言えませんが、直ちに NG ということはないと思います。


C++ で機能が同じでも混ぜてはいけない型を定義する方法として opaque alias という言語機能を用意しようという提案が出ています。 現在の言語機能である usingtypedef で付けた別名は本当にただ別の名前も用意するというだけですが opaque alias は違う型と判断される別名です。

opaque alias の提案は受け入れられる見込みは薄いらしいのですが、専用の機能を用意しなくても (まさにここで fana 氏が考えているように) なんとかする方法があるので記述が冗長な部分は将来に導入される見込みがあるリフレクション系の機能と組み合わせて解決すればよかろうという理由によるものであって、型を分けることを否定しているわけではありません。

要するにやり方はともかくとして別の型として分けたいと考える人は割といるということです。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#5

fana

総合スコア11663

投稿2023/07/04 10:00

opaque alias

こういう単語を示していただけると,足掛かりにできて助かります.
感謝.

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#6

fana

総合スコア11663

投稿2023/07/06 02:28

自分の間違いのチェック用みたいな役目であれば,自分がそこを実装している期間は使えば良く,役目を終えたならば

using WorldVec = Vec2< CoordinateSystem::World >;

をそっと

using WorldVec = Vec2; //汎用の「ベクトル」

に変えて原状回復してやればいいのかな.
つまり(?),

「そんなの変数名とかで表現すれば十分じゃね? 不足ならコメントでも書けと」

に加えて,型名も using/typedef 噛ませてエイリアス側を使っとけば便利,と(←なんだかとても当たり前のことだな).

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#7

can110

総合スコア38266

投稿2023/07/06 04:08

#1 の補足です。

今回の例であがっているベクトルでいうとワールド、カメラ以外にも

  • 極座標なのか直交座標なのか
  • 極座標なら単位は度なのかラジアンなのか(直交座標ならmmなのかmなのか)

といったさらに独立した「混ぜるな危険」な概念がいろいろ考えらえるので、それらを型でどうにかというのは無理筋だと思います。
ベクトルはカメラや球といったモノの中に隠蔽しておいて、必要な演算、操作は各モノの関数を介しておこなうのがよいと思います。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#8

fana

総合スコア11663

投稿2023/07/06 05:25

うーん,何でしょう? 例に挙げていただいたような概念についてだと「99.728% くらいそこは間違わないところ」のように思うんですよね(自分でも何やら矛盾したようなことを言っている気がします.不思議).
(そんなこと言うなら,#3 の例だって「間違わないところ」なハズであるくせに)

原理主義的にとにかく型を分けるのではなく,何らかの必要性があって分ける.
じゃあ,その要不要の線引きはどこからくるのか? …というと「期待される間違いやすさ」の度合い…なのかな?

ベクトルはカメラや球といったモノの中に隠蔽しておいて

その場合,その中身を実装する際の話になりますね.

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#9

SaitoAtsushi

総合スコア5466

投稿2023/07/06 06:38

数式を立ててしまえばあとは数学のルールに従えばよいのが数学の抽象というものですが、プログラムとしての整理の仕方としては物の側に焦点を当てた抽象化したほうが使いやすいわけです。

数学世界の上に物理世界のガワを被せるというのがプログラムとしての自然な抽象だと考えた場合には質問中に提示されたコード例は数学世界でも物理世界でもない半端な位置づけなのが不自然とは言えます。

ただ……そう理想的にすっきりとレイヤを分けられるものでもないのも現実です。 更に言うなら私はここに数学と物理という境界線を見出しましたけれども他の形の境界線を引いたほうが分かりやすいこともあるでしょうし、もっとたくさんの境界線 (レイヤ) で区切ったほうが良いこともあるのかもしれません。

結局のところは場合によって程度を見極めるべき、プログラムごとの総合的な判断によるべきと私は考えるので「やり始めたらキリがない」というのは理由としては弱いように思います。 キリがないならほどほどの (現実的に有用だと思う程度の) ところでやめればよいだけなので。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#10

fana

総合スコア11663

投稿2023/07/12 02:13

編集2023/07/12 02:17

いまさらですがコレ,行列の中の要素の実装を「行ベクトルが並んだもの」として実装したい(ベクトルの実装を流用したい)ときには
Vec2<???> RowVecs[2]; //←ここの型は何なの?
ってなってしまい,ちょっと嫌ですね.
(「2x2 の回転行列」みたいな単純なのであれば定められるけども)

まぁ,template な型にした元々の意図を鑑みれば,そもそもそいつらをその意図と無関係な箇所にまで持ち込むべきではなく,上記のような実装流用は考えちゃいけないのだろうとは思うが…

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

最新の回答から1ヶ月経過したため この意見交換はクローズされました

意見をやりとりしたい話題がある場合は質問してみましょう!

質問する

関連した質問