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

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

新規登録して質問してみよう
ただいま回答率
85.41%
C++

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

Q&A

解決済

4回答

386閲覧

templateの非型テンプレートパラメータでメンバ関数を部分特殊化したい

do_a

総合スコア2

C++

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

2グッド

0クリップ

投稿2024/02/16 00:34

実現したいこと

C++の勉強をかねて、数学のベクトルをプログラムで表現してみようと思い立ちました。
そこで、下記のようなコードを書いています。

外積に関しては、ベクトルが3次元のときだけ外積の計算結果ベクトルを返すようにしたいです。一応、2次元のときの内×内、外×外の和の値はオプション程度で書いてみました。
↑これらは次元数が2か3のときしか必要ない為、下記コード中DIMENSIONが2,3のときだけ外積計算の関数を追加した、部分特殊化テンプレートを書くことになります。

発生している問題・分からないこと

テンプレートの特殊化は初めての経験なのですが、コード書いていて外積以外の処理も特殊化の方で書かないといけないのが面倒で、うまい書き方は無いのかなと思っています。
クラスの継承みたいに、追加する内容だけ書けたら、と思うのですが、何か方法ありますでしょうか。

該当のソースコード

C++

1template<class R, int DIMENSION> 2class VECTOR { 3private: 4 R val[DIMENSION]; 5 6public: 7 VECTOR() {} 8 VECTOR(const R r[DIMENSION]) { 9 for (int i = 0; i < DIMENSION; i++) { 10 val[i] = r[i]; 11 } 12 } 13 VECTOR(array<R, DIMENSION>& r) { 14 for (int i = 0; i < DIMENSION; i++) { 15 val[i] = r[i]; 16 } 17 } 18 19 20 21 //cast 22 operator array<R, DIMENSION>() const { 23 array<R, DIMENSION> ret; 24 for (int i = 0; i < DIMENSION; i++) ret[i] = val[i]; 25 return ret; 26 } 27 28 29 30 //operator 31 //assignment 32 VECTOR<R, DIMENSION>& operator=(const VECTOR<R, DIMENSION>& vec) { 33 for (int i = 0; i < DIMENSION; i++) this->val[i] = vec[i]; 34 return *this; 35 } 36 //index 37 R operator[](const int i) { 38 return this->val[i]; 39 } 40 //addition 41 VECTOR<R, DIMENSION>& operator+(const VECTOR<R, DIMENSION>& vec) { 42 VECTOR<R, DIMENSION> ret; 43 for (int i = 0; i < DIMENSION; i++) ret = this->val[i] + vec.val[i]; 44 return ret; 45 } 46 //reduction 47 VECTOR<R, DIMENSION>& operator-(const VECTOR<R, DIMENSION>& vec) { 48 VECTOR<R, DIMENSION> ret; 49 for (int i = 0; i < DIMENSION; i++) ret = this->val[i] - vec.val[i]; 50 return ret; 51 } 52 //inner product 53 R operator*(const VECTOR<R, DIMENSION>& vec) { 54 R ret = 0; 55 for (int i = 0; i < DIMENSION; i++) ret += this->val[i] * vec.val[i]; 56 return ret; 57 } 58}; 59 60template<class R> 61class VECTOR<R, 3> { 62 63 //ここにプライマリのクラスと同じ内容を書いている。 64 //これが面倒。 65 66 //outer product 67 VECTOR<R, 3>& outer_product(const VECTOR<R, 3>& vec) { 68 VECTOR<R, 3> ret; 69 ret[0] = this->val[1] * vec.val[2] - this->val[2] * vec.val[1]; 70 ret[1] = this->val[2] * vec.val[3] - this->val[3] * vec.val[2]; 71 ret[2] = this->val[0] * vec.val[1] - this->val[1] * vec.val[0]; 72 return ret; 73 } 74}; 75 76template<class R> 77class VECTOR<R, 2> { 78 79 //ここにプライマリのクラスと同じ内容を書いている。 80 //これが面倒。 81 82 //outer product 83 R outer_product(const VECTOR<R, 2>& vec) { 84 R ret; 85 ret = this->val[0] * vec.val[1] - this->val[1] * vec.val[0]; 86 return ret; 87 } 88};

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

特定のメンバ関数のみ特殊化するというのもありましたが、部分特殊化の場合は使えないようですね。

関数オブジェクトをstructで包んで...みたいなのもありましたが、2次元と3次元で戻り値の型が違うので、どうしたものかと。

是非宜しくお願い致します。

補足

特になし

SaitoAtsushi👍を押しています
fanaを押しています

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

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

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

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

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

guest

回答4

0

簡略化した例で示します。

クラステンプレートのテンプレート引数を使ってテンプレートではないメンバ関数を std::enable_if で無効にするということは出来ません。 クラステンプレートが実体化するときにメンバ関数も解釈されてしまい単に誤った定義であるということになってしまいます。

実引数にマッチするテンプレートを発見できない (ので展開されない) というのとテンプレートを展開した結果に間違いが含まれるというのは別の事象だということを意識するとわかりやすいのではないかと思います。

無意味な引数でメンバ関数もクラステンプレートにするという方法をとればメンバ関数の実体化はメンバ関数を使うときまで遅延され、おおよそ期待するような結果と言えるのではないかと思います。

cpp

1#include <iostream> 2#include <type_traits> 3 4template <int N> 5class foo { 6 private: 7 int data; 8 9 public: 10 foo() : data(42) {} 11 template <int T = 2> 12 typename std::enable_if<N == T, void>::type print_data(void) { 13 std::cout << N << " " << data << std::endl; 14 } 15 template <int T = 3> 16 typename std::enable_if<N == T, int>::type print_data(void) { 17 std::cout << N << " " << data << std::endl; 18 return 1; 19 } 20}; 21 22int main(void) { 23 foo<1> x; 24 // x.print(); // これは出来ない 25 // auto bar = x.print_data(); // 当然ながらこれも出来ない 26 foo<2> y; 27 y.print_data(); 28 // auto baz = y.print_data(); // 返却値が void なので値を返さない 29 foo<3> z; 30 z.print_data(); 31 auto qux = z.print_data(); // int 型の値を返す 32}

投稿2024/02/16 08:21

SaitoAtsushi

総合スコア5544

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

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

do_a

2024/02/26 04:09 編集

すみません、お返事遅くなりました。 ありがとうございます。 ううん、少し自分には難しかったのですが、無意味な引数Tを使うことで、 23, 26, 29行目の foo<*> x; ではまだメンバ関数が実体化されない  → *.print_data 関数を呼び出し時に初めてNとTの比較結果を基に関数が作成される ということになり、結果コンパイル通ってくれる!というようなイメージでしょうか。
SaitoAtsushi

2024/02/27 12:57

おそらく概略としては理解できていると思います。 私の説明には厳密ではない部分があり、本来なら名前のルックアップなどの文法解釈と実体化は分けて考えなければなりません。 クラステンプレートが実体化されてもそのメンバ関数は (使われない限り) 実体化はされないのですが、実体化されないからといって全く解釈されずに読み飛ばされるわけではなく辻褄の合わないところはその時点で検出されるので実体化の概念にまとめて単純化してしまいました。 C++ の挙動は入り組んでいて、きちんと理解しようとすると本一冊分くらいになるのでここで説明できる程度の分量では大幅に単純化した形になってしまいます。 私が示したのはあくまでも (質問の状況で使える) 単発のイディオムとして捉えていただき、そのメカニズムについてはキッチリした解説書を参考にすることをお勧めします。
do_a

2024/03/04 03:53

ありがとうございます。 少しイメージが鮮明になっただけでも大変助かります。
guest

0

https://stackoverflow.com/questions/22486386/partially-specializing-a-non-type-template-parameter-of-dependent-type

を参考になんとなく書いてみました。
定数を型に変換するstd::integral_constantを使って型テンプレートパラメータにしてるようです。

C++

1#include <iostream> 2#include <type_traits> 3using namespace std; 4template <typename T, typename U> 5struct VECTOR_; 6template <typename T, typename V, V N> 7struct VECTOR_<T, integral_constant<V, N>> { 8 void hoge() {cout << "not implemented" << endl;} 9}; 10template <typename T> 11struct VECTOR_<T, integral_constant<int, 2>> { 12 void hoge() {cout << 2 << endl;} 13}; 14template <typename T> 15struct VECTOR_<T, integral_constant<int, 3>> { 16 void hoge() {cout << 3 << endl;} 17}; 18template <typename T, int N> 19struct VECTOR : VECTOR_<T, integral_constant<int, N>> {}; 20int main() 21{ 22 VECTOR<int, 2> v2; 23 VECTOR<int, 3> v3; 24 VECTOR<int, 4> v4; 25 v2.hoge(); 26 v3.hoge(); 27 v4.hoge(); 28 return 0; 29} 30// 2 31// 3 32// not implemented

回答したあとですみませんが、問題を勘違いしていました。
タイトルから非型パラメータで特殊化できないのかと思って、わざわざ型にしたのですが、定数のままでも特殊化できるんですね。
まあ参考情報にでもしてください。問題が演算子オーバーロード時の戻り値型と共通化可能なメソッド/演算子なのであれば、継承してあげれば共通化でき、演算子はグローバル定義にでもすれば解決するのではないでしょうか?

参考実装もつけておきます。

C++

1#include <iostream> 2#include <type_traits> 3using namespace std; 4template <typename T, int N> 5struct VECTOR_common { 6 T vec[N]; 7 void hoge() {cout << "hoge" << endl;} 8}; 9template <typename T, int N> 10struct VECTOR: VECTOR_common<T,N> {}; 11template <typename T, int N> 12VECTOR<T,N> operator+(const VECTOR<T,N>& left, const VECTOR<T,N>& right) { 13 cout << "<T,N>" << endl; 14 VECTOR<T,N> res(left); 15 for (int i = 0; i < N; ++i) res.vec[i] += right.vec[i]; 16 return res; 17} 18template <typename T> 19VECTOR<T,3> operator+(const VECTOR<T,3>& left, const VECTOR<T,3>& right) { 20 cout << "<T,3>" << endl; 21 return VECTOR<T,3>(); 22} 23int main() 24{ 25 VECTOR<int, 2> v2; 26 VECTOR<int, 3> v3; 27 VECTOR<int, 4> v4; 28 v2.hoge(); 29 v3.hoge(); 30 v4.hoge(); 31 v2 = v2 + v2; 32 v3 = v3 + v3; 33 v4 = v4 + v4; 34 return 0; 35} 36// hoge 37// hoge 38// hoge 39// <T,N> 40// <T,3> 41// <T,N>

投稿2024/02/16 03:31

編集2024/02/16 07:04
dameo

総合スコア999

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

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

do_a

2024/02/26 03:59

> 演算子はグローバル定義にでもすれば解決するのではないでしょうか? @fana 様と同様、外積はメソッドじゃなく、もうクラスの外に出しゃあいい、という意見ですね。 その通りだと今思っています。 まだうまく使い分けができてませんね。勉強不足です。 ありがとうございます!
dameo

2024/02/26 04:02

> @fana 様と同様、外積はメソッドじゃなく、もうクラスの外に出しゃあいい、という意見ですね。 演算子も含めその通りです。
guest

0

ベストアンサー

素人ながら,こんなのを考えましたけど,何だかスマートではないというか,もっとちゃんと(?)できないのかなぁ,みたいな……

方針:

  • 2Dと3Dとで外積の戻り値の型が違う点に対しては,「外積の戻り値の型」だけを定義する用のtemplateクラスを用意して,2Dと3Dに対してコレを特殊化することで対処できないか?
  • 外積のメソッド自体は VECTOR に設けるが,その実装は2Dと3Dでしかコンパイルが通らないものになっていれば,別の次元で外積のメソッドを使おうとするとコンパイルエラーになる,という方針で誤魔化す方向で.(ただしコンパイルエラーのメッセージは全く意味不明なものになりそう)

C++

1//外積の戻り値の型を決める用 2template< class R, int DIMENTION > 3struct OuterProdRetType{ using type = void; }; 4 5// 6template<class R, int DIMENSION> 7class VECTOR { 8 //※(省略) 9 10 //外積.戻り値の型に↑のやつを使っている 11 typename OuterProdRetType<R, DIMENSION>::type outer_product(const VECTOR<R, DIMENSION>& vec) const; 12}; 13 14 15//--------------------------- 16//for 2D 17template< class R > 18struct OuterProdRetType<R,2>{ using type = R; }; 19 20template< class R > 21R outer_product_(const VECTOR<R, 2>& V1, const VECTOR<R, 2>& V2 ) 22{ return V1[0] * V2[1] - V1[1] * V2[0]; } 23 24//--------------------------- 25//for 3D 26template< class R > 27struct OuterProdRetType<R,3>{ using type = VECTOR<R, 3>; }; 28 29template< class R > 30VECTOR<R, 3> outer_product_(const VECTOR<R, 3>& V1, const VECTOR<R, 3>& V2 ) 31{ 32 VECTOR<R, 3> ret; 33 ret[0] = V1[1] * V2[2] - V1[2] * V2[1]; 34 ret[1] = V1[2] * V2[3] - V1[3] * V2[2]; 35 ret[2] = V1[0] * V2[1] - V1[1] * V2[0]; 36 return ret; 37} 38 39//--------------------------- 40//外積メソッド定義 41//2Dか3Dでないとこのメソッドを使うコードを書いたらコンパイル通らない. 42//ただしコンパイルエラーのメッセージは全く意味不明になりそう. 43template< class R, int DIMENSION > 44typename OuterProdRetType<R, DIMENSION>::type VECTOR<R, DIMENSION>::outer_product(const VECTOR<R, DIMENSION>& vec) const 45{ return outer_product_( *this, vec ); }

投稿2024/02/16 03:05

編集2024/02/16 03:06
fana

総合スコア11784

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

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

fana

2024/02/16 03:12 編集

(別の話なので回答を分けました) いろいろこねくり回してみたけど,「自分では使いたくねぇなぁ」みたいなのになった… 力不足すぎる. (このくらい不格好なコードを晒しておけば,きっと玄人が黙ってはいないだろう! という期待!)
fana

2024/02/16 04:36

ところで,こんなことを言うと問題そのものを全否定することになっちゃうけど, 外積とか(他の大抵のoperator群もそうだけども)別に「メソッド」にしなくてもよくね? とか思わんでもない. V1.outer_product( V2 ); //外積がメソッド outer_product( V1, V2 ); //外積を計算するただの関数 の前者じゃないと{困る/嫌だ/etc}とかいうことが果たしてあるのだろうか? と. 後者でも良いのであれば,単に2Dと3Dの外積の関数を書けば済むのだし.
do_a

2024/02/26 03:54

> 外積とか(他の大抵のoperator群もそうだけども)別に「メソッド」にしなくてもよくね? とか思わんでもない. 確かに、それはおっしゃる通りですね... 必要性を十分考えてないコードだなぁと自分でも感じました。 色々アイデアを与えて下さりありがとうございます。
fana

2024/02/26 04:44 編集

これまた問題と関係ない話になりますが… 内積 ← * なる演算子で書ける 外積 ← outer_product という名前で書かないとダメ …という非対称性がすっきりしないのである. だったら内積も inner_product とか書くことにするか? っていうと,「うーん,そこは * で書きてぇ…」みたいな.(演算子じゃないメソッドにすべき場面だと思うけども,内積は何故か * で書きたい誘惑みたいなのが強い) そしたら「外積側を演算子で書きたい」と思うわけだけども,そうなると「果たして何を使うとしっくりくるのか?」という問題がどうにもうまく解決できない. 昔,外積用に強引に ^ を使ってみたら, * まずこれが外積だということ自体がコード見て分かり難いよね * それ以前に演算子の優先順位が低いことが厄介すぎる …っていう感じでダメだった.
do_a

2024/03/04 03:58

ありがとうございます。 そうなんですよね、似たようなこと考えました... 最初、「行列同士の割り算なんてないから...」なんてとんでもないことを考えて(まあやってみたいだけだったのでいいのですが)、書いてみたら、実数との割り算と視覚的に区別しにくいので地獄だなと感じました。他の演算子でいいものないかなとか考えてましたが、おっしゃる通り結局どちらも inner_product() or dot() outer_product() or cross() とかのほうが断然よさそうですね。
guest

0

※特殊化云々に取り掛かるよりも前に,まず,template<class R, int DIMENSION> class VECTOR のコードとしてまともな物を示して欲しいところです.


で,本題についてですが,
「同じものを書くのが面倒」というだけの話であれば,その部分は以下のように単にコピペで済ませれば,とりあえずは楽になるのではないでしょうか.
(もっとまともな方法が欲しい感がすごいですが……)

C++

1template<class R, int DIMENSION> 2class VECTOR { 3#include "VectorImpl.h" //※ここに存在したコードをまるごと "VectorImpl.h" に移した 4}; 5 6template<class R> 7class VECTOR<R,3> 8{ 9private: 10 static constexpr int DIMENSION = 3; //コピペするコードに DIMENSION が存在することへの対策 11#include "VectorImpl.h" //※コピペで済ます 12 13public: 14 //outer product 15 VECTOR<R, 3>& outer_product(const VECTOR<R, 3>& vec) { 16 VECTOR<R, 3> ret; 17 ret[0] = this->val[1] * vec.val[2] - this->val[2] * vec.val[1]; 18 ret[1] = this->val[2] * vec.val[3] - this->val[3] * vec.val[2]; 19 ret[2] = this->val[0] * vec.val[1] - this->val[1] * vec.val[0]; 20 return ret; 21 } 22};

投稿2024/02/16 01:54

fana

総合スコア11784

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

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

do_a

2024/02/16 02:05

>> ※特殊化云々に取り掛かるよりも前に,まず,template<class R, int DIMENSION> class VECTOR のコードとしてまともな物を示して欲しいところです. そうですね、失礼しました。 #includeってそういう使い方もできたのですか... >> (もっとまともな方法が欲しい感がすごいですが……) そうですね、ちょっとイレギュラーな書き方に見えますよね。 普通にハードコードしてもいいかもしれませんね。 ありがとうございました。
do_a

2024/02/16 02:31 編集

>> ※特殊化云々に取り掛かるよりも前に,まず,template<class R, int DIMENSION> class VECTOR のコードとしてまともな物を示して欲しいところです. ↑ちなみに、これってVECTORがベクトルとして機能的に不足しているとか、operator+,-がおかしいとか、その辺諸々ちゃんとしなさいよ、ということで良かったですか? 次他の質問するときに、同じ間違いしたくなくて💦 すみません。
fana

2024/02/16 02:21

メソッド使おうとすると,コンパイル通らないとか,一時オブジェクトの参照を返しているとか,まずもって「ふつうに使えない」状態のコードになっている,という意味です. 特殊化を考える前に「外積は無いけど他は普通に使える」という完成状態のコードを用意するべきでしょう.
do_a

2024/02/16 02:33

そうですよね、おっしゃる通りです。 質問出す直前に色々いじってしまったのに、確認し忘れていました。 ご教示ありがとうございます。失礼致しました。
fana

2024/02/16 04:43

どうでもいいけど > "VectorImpl.h" という名前は「ごく普通のヘッダファイルですよ?」みたいな雰囲気を醸し出してしまう気がするから, こういうことする場合には避けた方が良いかも.
do_a

2024/02/26 03:45

お返事遅れました。 そうですね、他の人に見せる時のことを考えると、いろいろとはらみそうです... ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.41%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問