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

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

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

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

Q&A

解決済

2回答

6395閲覧

テンプレート引数であるvectorの型をテンプレートで取得したい

退会済みユーザー

退会済みユーザー

総合スコア0

C++

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

0グッド

0クリップ

投稿2020/07/06 06:25

編集2020/07/06 06:43

質問内容

std::vectorをユニバーサル参照で受け取り
そのvectorの要素の型と同じ変数varを作り
その新たに作成したvectorにコピー/ムーブ
したあとそれらを使って
処理を行う関数を作りたいのですが
エラーが発生してしまいます。
どうすればvector内の要素の型を取得できますか。

どうかよろしくお願い致します。

c++

1#include <utility> 2#include <vector> 3 4template<class Vec,typename Type> 5void func(Vec<Type>&& vec){ 6 Type var = 0;// ← Typeが推論できない 7 auto vec2 = std::forward<Vec>(vec); 8 // 9 // 変数varやvec2を使った処理が続く(省略) 10 // 11} 12 13int main(void) 14{ 15 std::vector<int> left_ref = {1,2,3,4,5}; 16 func(left_ref);//左辺値 17 18 func(std::vector<int>{{1,2,3,4,5}});//右辺値 19 return 0; 20} 21

上記コードのエラーコード

terminal

1/***/main.cpp:5:11: error: ‘Vec’ is not a template 2 void func(Vec<Type>&& vec){ 3 ^~~ 4/***/main.cpp: In function ‘int main()’: 5/***/main.cpp:16:18: error: no matching function for call to ‘func(std::vector<int>&)’ 6 func(left_ref);//左辺値 7 ^ 8/***/main.cpp:5:6: note: candidate: template<class Vec, class Type> void func(Vec&&) 9 void func(Vec<Type>&& vec){ 10 ^~~~ 11/***/main.cpp:5:6: note: template argument deduction/substitution failed: 12/***/main.cpp:16:18: note: couldn't deduce template parameter ‘Type’ 13 func(left_ref);//左辺値 14 ^ 15/***/main.cpp:18:39: error: no matching function for call to ‘func(std::vector<int>)’ 16 func(std::vector<int>{{1,2,3,4,5}});//右辺値 17 ^ 18/***/main.cpp:5:6: note: candidate: template<class Vec, class Type> void func(Vec&&) 19 void func(Vec<Type>&& vec){ 20 ^~~~ 21/***/main.cpp:5:6: note: template argument deduction/substitution failed: 22/***/main.cpp:18:39: note: couldn't deduce template parameter ‘Type’ 23 func(std::vector<int>{{1,2,3,4,5}});//右辺値 24 25

エラー内容は

Vecはテンプレートではない。
funcの使い方がおかしい。
テンプレート引数の控除/置換に失敗しました。
テンプレートパラメータ「Type」を推定できませんでした。

とのことですが
ひょっとかしたらc++ではテンプレート引数のテンプレート引数
を推論できないのかな?
といったかんじです。

試したこと

c++

1#include <utility> 2#include <vector> 3#include <memory> 4#include <iostream> 5 6template<typename Type> 7void func(const std::vector<Type>& vec){ 8 Type var = 0; 9 auto vec2 = vec; 10 // 11 // 変数varやvec2を使った処理が続く(省略) 12 // 13 14 std::cout << "コピー" << std::endl; 15} 16 17template<typename Type> 18void func(std::vector<Type>&& vec){ 19 Type var = 0; 20 auto vec2 = std::move(vec); 21 // 22 // 変数varやvec2を使った処理が続く(省略) 23 // 24 25 std::cout << "ムーブ" << std::endl; 26} 27 28int main(void) 29{ 30 std::vector<int> left_ref = {1,2,3,4,5}; 31 func(left_ref);//左辺値 32 33 func(std::vector<int>{{1,2,3,4,5}});//右辺値 34 return 0; 35}

上記のようにオーバーロードで2つの関数を作れば
コンパイルできますが
std::forwardとユニバーサル参照を使って1つの関数で
済ましてしまいたいです。

開発環境の備考

ツールの種類ツールの名前バージョン
コンパイラclang++6.0.0
OSLinux Mint18.3

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

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

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

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

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

guest

回答2

0

テンプレートを受け取る場合にはそのための記法があります。 テンプレートテンプレートと呼ばれているのでこれをキーワードにして検索してみるといいでしょう。

たとえばこのようになります。

cpp

1#include <utility> 2#include <vector> 3 4template<typename Type, typename Al, template<class,class>class Vec> 5void func(Vec<Type,Al>&& vec){ 6 Type var = 0;// ← Typeが推論できない 7 auto vec2 = std::move(vec); 8 // 変数varやvec2を使った処理が続く(省略) 9} 10 11int main(void) 12{ 13 // ユニバーサル参照ではないので左辺値参照に非対応 14 // std::vector<int> left_ref = {1,2,3,4,5}; 15 // func(left_ref);//左辺値 16 17 func(std::vector<int>{{1,2,3,4,5}});//右辺値 18 return 0; 19}

ただし、このときの && はユニバーサル参照にはならないので左辺値の場合と右辺値の場合を個別に書く必要があり、普通は避けた方が好ましいです。

なので、一般的には yohhoy 氏が提案しているような方法が使われます。

投稿2020/07/06 07:25

SaitoAtsushi

総合スコア5437

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

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

yohhoy

2020/07/06 07:45

テンプレート・テンプレートによる解法は、std::vectorは2個のテンプレートパラメータをとるという事前知識が必要になるので、おすすめし辛いことが多いですね :P FYI: C++17だったか、どこかのタイミングで template<typename Type, template<class>class Vec>/Vec<Type> が許容されるようになっています。 https://wandbox.org/permlink/QRMttzMtCML1f5EX
退会済みユーザー

退会済みユーザー

2020/07/06 14:00 編集

SaitoAtsushi様 返信に時間がかかってしまい申し訳ございません。 貴重なご意見ありがとうございます。 テンプレートテンプレートという言葉は初耳でしたので 難しそうですがちょっと調べてみます。 ご回答誠にありがとうございました。
guest

0

ベストアンサー

どうすればvector内の要素の型を取得できますか。

std::vector<T>コンテナが格納する値の型Tは、メンバ型value_type経由で取得できます。
(C++標準ライブラリ提供のコンテナクラスでは、同様にvalue_typeを提供します)

C++

1#include <type_traits> 2 3template<class Vec> 4void func(Vec&& vec){ 5 using Type = typename std::remove_cv_t<std::remove_reference_t<Vec>>::value_type; 6 // using Type = std::remove_cvref_t<Vec>::value_type; // C++20以降 7 Type var = 0; 8 9 //... 10}

https://qiita.com/7of9/items/6a3314af4a4369d85631 にあるコメントも参考にどうぞ。

投稿2020/07/06 06:52

編集2020/07/07 02:43
yohhoy

総合スコア6189

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

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

退会済みユーザー

退会済みユーザー

2020/07/06 14:46 編集

yohhoy様 ご回答ありがとうございます。 返信に時間がかかってしまい申し訳ございません。 上記のコードの ``` using Type = std::remove_cv_t<std::remove_reference_t<Vec>>::value_type; ``` の部分ではテンプレート型Vecの参照外しとcv修飾外しのあと vector内で定義された型value_type に別名をつけて使っているようですが なぜ参照外しとcv修飾外しを行う必要があるのか私の知識ではわかりません。 試しに ``` using Type = typename Vec::value_type; ``` と書いてみたら error: ‘std::vector<int>&’ is not a class, struct, or union type とエラーが出ました std::vector<int>&はクラス,構造体または共用体ではない とのことですが何が問題なのかさっぱりわかりません(汗) あと私の環境ではコンパイラにtypename付けろと怒られましたが ``` using Type = typename std::remove_cv_t<std::remove_reference_t<Vec>>::value_type; ``` こうすれば動きました。 どうかお返事お待ちしております。
yohhoy

2020/07/07 02:50

> なぜ参照外しとcv修飾外しを行う必要があるのか私の知識ではわかりません。 改めて見直すと、cv修飾外し remove_cv_t の必要性はありませんでした。 一方で、参照外し remove_reference_t は必ず必要となります。 > using Type = typename Vec::value_type; > と書いてみたら > error: ‘std::vector<int>&’ is not a class, struct, or union type > とエラーが出ました これは、Forwarding Referenceで行われるテンプレートパラメータ型推論の仕様によります。 呼び出し側で左辺値(left_ref)を指定すると Vec=std::vector<int>& に推論され、参照型std::vector<int>&それ自身ではvalue_typeメンバ型を提供しないためエラーとなります。 関数テンプレート内部実装で参照外し remove_reference_t を行うのはこのためです。 (呼び出し側に右辺値を指定した場合は、Vec=std::vector<int> に推論されるため、こちらだけを考えるなら参照外しが無くとも動きます。) > あと私の環境ではコンパイラにtypename付けろと怒られました 現在(C++17)は typename キーワード指定が必要でした。将来的(C++20)には省略可能となります。
退会済みユーザー

退会済みユーザー

2020/07/07 08:15 編集

yohhoy様 御返事誠にありがとうございます。 下記コード下部のように クラス内で定義された型へアクセスする際 そのクラスの参照からアクセスできないので 参照外しを行っているという解釈でよろしいでしょうか また本題から少しずれてしまうかと思われますが 私はクラス内で定義された型へのアクセス方法 はstaticメンバと同じようなものだと 思っていたのですが下記コードで試してみたら けっこう違うみたいですね… あと追加の質問で申し訳ないのですが 質問文のようにユニバーサル参照に左辺値を入力すると std::vector<int>&となるとおっしゃっていましたが 引数の内部変数の値を変更したくない時 const std::vector<int>& と推測させることは できるのでしょうか。 --------------------------------------------------------------------------------------------------------------------------------------------- #include <vector> #include <type_traits> template<typename Type>struct A{using MyType = Type;};//型を保持する構造体A int main(void){ A<int> a; typename a::MyType var1 = 0;// (エラー発生)オブジェクトからMyTypeにはアクセスできない。 typename A<int>::MyType var2 = 0;// 実体なしのクラスからならアクセスできる。 typename A<int>&::MyType var3 = 0;// (エラー発生)Aの参照からMyTypeにはアクセスできない。 // 参照外しをすれば A<int>::MyType と等しくなる。 typename std::remove_reference_t<A<int>&>::MyType var4 = 0; return 0; } --------------------------------------------------------------------------------------------------------------------------------------------- どうかお返事お待ちしております。
yohhoy

2020/07/07 11:40 編集

> そのクラスの参照からアクセスできないので参照外しを行っているという解釈でよろしいでしょうか はい。 XXX::value_type の XXX部 には、厳密にクラス型が要求されます(今回のようにクラス型への参照型はNG) > 引数の内部変数の値を変更したくない時 const std::vector<int>& と推測させることはできるのでしょうか。 呼び出し側の左辺値(left_ref)が const std::vector<int> 型ならば、Vec=const std::vector<int>&と推論されます。 そうではなく、呼び出し側を std::vector<int> としたまま const std::vector<int>& を推論したいとう意味であれば、そもそもForwarding Referenceの利用が適切ではありません。最初から const T& とすればよい訳ですから。
退会済みユーザー

退会済みユーザー

2020/07/08 06:30 編集

yohhoy様 度々御返事頂き誠にありがとうございます。 >そもそもForwarding Referenceの利用が適切ではありません。 上記の部分が私にはわからなかったので 私の解釈が正しいか確認をお願いします。 完全転送を使った関数やメソッドでは 右辺値が入力された時推論される型は非constとなり(非constメンバにアクセスできる) なおかつ左辺値が入力された時も 同じコードでなければならないのでユニバーサル参照で Vec=const std::vector<int>&と推論させたい (右辺値が入力された場合非constメンバにアクセスできるのに 左辺値が入力された場合非constメンバにアクセスできない) ということ自体がおかしい。 という解釈でよろしいでしょうか。 どうかお返事お待ちしております。
yohhoy

2020/07/08 05:34 編集

diodeさんの解釈でそう外してないように読めました。 Forwarding Referenceの第一目的は、「上位側から与えられた実引数を、左辺値は左辺値として/右辺値は右辺値として、下位関数へ引き渡すため」に用いられるジェネリックな仕組みです。 通常は std::forward 関数テンプレートと組み合わせてこれを実現します。 あるジェネリックな関数が右辺値を受け取るということは、関数内部でムーブセマンティクスの利用を前提とする、つまり実引数に対して破壊的操作を加える(=ムーブで値を取り出す)前提があると見なせます。 「const std::vector<int>&と推論させたい」は、右辺値と左辺値で異なる操作を行いたいという解釈にもなるため、先コメント通りの内容となります。 # 本当にやりたい処理を理解してはいないため、"適切でない"は少々言い過ぎだったかもしれません。 # 私が勝手に読み取った目的の範囲において、別の実装の方が適しているように思えたという程度です。
退会済みユーザー

退会済みユーザー

2020/07/08 19:52 編集

yohhoy様 度々御返事頂き誠にありがとうございます。 返信に時間がかかってしまい申し訳ございません。 >本当にやりたい処理を理解してはいないため、"適切でない"は少々言い過ぎだったかもしれません。 質問文で私が書いたコードは色々省略しすぎて わかりづらくなってしまっていたと思うので さらに具体的なコードを書きました。 https://wandbox.org/permlink/G43xHmFyQkU4vW0s の145行目から173行目までのように +演算子のオーバーロードを 左辺値が渡された場合const参照と推論できたら より安全なコードにできるなと考えていた ということです。 この場合+operator内で引数の非constメンバにアクセスしていなように見えるので const参照と推論できたらより安全になると思うのですが それを実現する方法が無い(できたら完全転送の考え方と矛盾している) ように感じるので安全性を重視するなら 質問文内の項目[試したこと] のようにオーバロードを2つ作る方法を とるべきなのでしょうか。 短く書きたかったのですが結構大きなコードになってしまいました すいません。 あと本題のvectorの要素の型を取得したいという 質問自体は解決できたので問題を解決済みとさせていただきます。 お手数お掛けしてしまいますが どうかお返事お待ちしております。
yohhoy

2020/07/09 01:27 編集

設計問題なので絶対的な正解はありませんが、一時オブジェクト生成の回避を主目的にした場合、自分ならこうするかなという設計だけ共有しておきます: - M::operator+=(const M&)オーバーロードを実装する(a += b; でaをin-place更新) - M::operator+(const M& l, M&& r)とM::operator+(M&& r, const M& l)では return std::move(r) += l; - M::operator+(const M& a, const M& b) では return M{a} += b; エッジケースを考えるとさらにoperator+オーバーロードが必要かもしれません。複雑なテンプレートメタプログラミングよりは素直にオーバーロード追加していくほうが簡潔になるだろうという方針です。
退会済みユーザー

退会済みユーザー

2020/07/09 04:39

yohhoy様 度々御返事頂き誠にありがとうございます。 確かにenable_ifの周りが 複雑になりすぎてすぎて私自身うっすら これ読み手にとって嫌がられる コードではないかな…と 心配していましたが アドバイス頂けとても助かりました。 この経験を私のコーディングスタイルに 反映していこうと考えております。 数日間お付き合いいただけ とても光栄に感じております。 大変ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問