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

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

新規登録して質問してみよう
ただいま回答率
85.48%
データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

C++

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

Q&A

解決済

4回答

615閲覧

汎用な型を取れる配列の各要素に、同じ処理を実行したい。

do_a

総合スコア2

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

C++

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

1グッド

0クリップ

投稿2024/02/05 06:41

編集2024/02/06 04:15

実現したいこと

複数の型を取れる配列の各要素に対し、その要素の型に依らず同じような処理を実行したい。

前提

言語はC++です。
下記の簡単なサンプルコードでは、複数の型のデータを一つの配列でまとめて、各要素に同じような処理を実行しています。
このコードでも問題はないのですが、test[i].typeの値で分岐しているところが冗長な気がします。
値のやりとりするとき毎回こんなキャストしなければいけないかとも思います。
long long 型にキャストして出力、というのは型に依らず同じ処理なので、一文にまとめられる上手い書き方があればいいな、というのが今回の質問です。
なお、今回void*には(unsigned )int, (unsigned )long long, charなどの数値型しか入らないとします。(キャスト不可な場合は考えない?)

該当のソースコード

C++

1//main.cpp 2 3#include <iostream> 4#include <vector> 5#include <stack> 6 7 8using namespace std; 9 10class TEST{ 11public: 12 void* val; 13 int type; 14}; 15 16int main(void) { 17 int a = 1; 18 char b = 'c'; 19 long long c = 10000; 20 21 22 TEST test[3]; 23 test[0].val = &a; 24 test[0].type = 0; 25 test[1].val = &b; 26 test[1].type = 1; 27 test[2].val = &c; 28 test[2].type = 2; 29 30 for (int i = 0; i < 3; i++) { 31 switch (test[i].type) 32 { 33 case 0: 34 cout << (long long)*(int *)test[i].val << endl; 35 break; 36 case 1: 37 cout << (long long)*(char *)test[i].val << endl; 38 break; 39 case 2: 40 cout << (long long)*(long long *)test[i].val << endl; 41 break; 42 default: 43 break; 44 } 45 } 46}

試したこと

templateやanyなどで解決できそうな気もしましたが、やはりうまい方法が思い浮かびません。可能なら、その方法をご教示願います。

↓こんなのもありました。これなら実現できますが、上記のレベルでここまで大層なことをしないといけないでしょうか。
https://qiita.com/mm_sys/items/9afb811c2e0c67a5cfd5
([C++] 異なる型を一つの配列で管理するクラス)

備考

あくまで知見を広めたい為の質問ですので、C++のバージョンなどの環境によって可能/不可能変わるような書き方でも是非教えて頂きたいです。
以上、宜しくお願い致します。

TN8001👍を押しています

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

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

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

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

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

fana

2024/02/06 02:30

あれ? 何か今,この質問にタグが一個もついてないですね. (そういう修正がなされたようにも見えないし…?)
do_a

2024/02/06 03:28

おかしいですね、確かに何もしていないのですが・・・?
TN8001

2024/02/06 04:04

@fanaさん > あれ? 何か今,この質問にタグが一個もついてないですね. たまに起きているteratailのバグだと思います(リニューアル後に気づきましたがそれ以前からあったかどうかは不明) 今年に入ってからこのくらいはすでに発生しています。 https://teratail.com/questions/2sxv3octga2770 https://teratail.com/questions/l0qbfodpzk46r4 https://teratail.com/questions/svpmv6gg7o8qvq https://teratail.com/questions/9wkvund7nlc9gw https://teratail.com/questions/plxd4duhkuadl0 https://teratail.com/questions/00acicyty497bk > (そういう修正がなされたようにも見えないし…?) 質問者がタグをすべて消すのってできないんですよね?(質問したことないのでわかりませんが^^; なんとなくBAした時・自己回答した時等、質問者が何かアクションをした時に(何らかの条件を満たしてしまうと)タグが消えてる気がします。 閲覧側からするとかなりイヤなのですが、質問者は悪くないし(BA後は見てないだろうし)また付け直してもらう(おそらく普通にできるはず)のも気が引けるんですよね^^; @do_aさん > おかしいですね、確かに何もしていないのですが・・・? よければ質問を編集して付け直してもらっていいですか?
do_a

2024/02/06 04:16

> よければ質問を編集して付け直してもらっていいですか? 付け直せたんですね。対応いたしました。
TN8001

2024/02/06 04:20

やっぱりできますね^^ わざわざすいませんでした。
fana

2024/02/06 04:46

なんだバグかぁ. タグで絞り込みかけてると,突然質問の一覧から消失するので「あれ?」ってなりますね.
guest

回答4

0

とりえる型の種類が事前にわかっているなら C++17 以降では std::variant を使えます。

cpp

1#include <iostream> 2#include <variant> 3 4int main(void) { 5 int a = 1; 6 char b = 'c'; 7 long long c = 10000; 8 9 std::variant<int, char, long long> test[3] = {a, b, c}; 10 for (auto& e : test) 11 std::cout << std::visit([](const auto& x) -> long long int { return x; }, e) << std::endl; 12}

型を有限個の種類に限定するのではなく「long long へ型変換可能な型」などとするなら標準ライブラリが提供するものだけでは丁度良いものがないので自前で準備しないといけないでしょう。

投稿2024/02/05 07:39

SaitoAtsushi

総合スコア5446

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

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

do_a

2024/02/05 23:49

御回答ありがとうございます。 variantですか! 少し調べてみましたが、visitor + lambdaですか、実行時の型で共通の処理を実行してくれるんですね。 勉強になります。
guest

0

ベストアンサー

TEST 型はコンストラクタ引数の型を見てそこで long long への変換方法をメンバに保持しちゃえば良いのではないだろうか?
……とか思ったのですが,どうなんでしょう?

C++

1class TEST 2{ 3private: 4 using ToLLf = long long(*)( void * ); 5 void *val; 6 ToLLf Cvt; //long long への変換手段をメンバに覚えておく 7 TEST( void *val, ToLLf Cvt ) : val(val), Cvt(Cvt) {} 8 9 template<class VAL_T> 10 static long long ToLL( void *val ){ return (long long)*(VAL_T*)val; } 11 12public: 13 //コンストラクタ引数の型に応じた変換手段をメンバに覚える 14 template< class VAL_T > 15 TEST( VAL_T *val ) : TEST( (void*)val, ToLL<VAL_T> ) {} 16 17 //コンストラクタに与えたポインタが指す先の値を long long 型にキャストした結果を取得 18 long long GetLL() const { return Cvt(val); } 19}; 20 21//main 22int main() 23{ 24 int a = 1; 25 char b = 'c'; 26 long long c = 10000; 27 28 TEST test[3]{ TEST(&a), TEST(&b), TEST(&c) }; 29 30 for( const auto &t : test ) 31 { std::cout << t.GetLL() << std::endl; } 32 33 return 0; 34}

投稿2024/02/05 11:04

fana

総合スコア11658

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

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

do_a

2024/02/06 00:08

ご回答ありがとうございます。 変換用のメンバ関数を宣言しておいて、インスタンス生成時にコンストラクタのタイミングで型を決めるていんですね! 個人的にとても新鮮なアイデアです。是非参考にさせてください。 ちなみに、知識不足で恐縮ですが、これってtestの要素数が増えるとその数だけ毎回ToLLを新しく定義するんでしょうか?(感覚的に、インスタンスの生成数だけToLLfに毎回GetLLをnewしているイメージ?) テンプレートの仕様を理解しきれていなくて...。なんとなくメモリのコストとかどうなっているんだろうと気になりました。 もしよろしければ、教えて頂けると幸いです。
do_a

2024/02/06 01:37 編集

追記・訂正 >> (感覚的に、インスタンスの生成数だけToLLfに毎回GetLLをnewしているイメージ?)  →(感覚的に、インスタンスの生成数だけCvtに毎回ToLLをnewしているイメージ?) と聞きたかったです。 また、デバッグモードで見ると、test[i].Cvtの参照先のアドレスが違っていたので、ToLLはインスタンス毎に生成されていますね。 ● これだと、ToLLの処理が長くなるほどメモリコストって大きくなるんでしょうか? ● また、ToLL は staticじゃないとエラーになりましたが、これってどうしてでしたっけ? 可能であれば、教えて頂けると幸いです。 宜しくお願い致します。
fana

2024/02/06 01:31 編集

> これってtestの要素数が増えるとその数だけ毎回ToLLを新しく定義するんでしょうか? template というのは,「なんつーかさ,こういうコードを書いて欲しいんだよね.必要なときに」というコンパイラへの指示というかそんなイメージです. この回答のコードの場合,TEST型インスタンスの個数は関係無くて,実際に使う型(:intとかcharとか)の種類分だけコンパイラ様が(前記の指示に従った形で)関数の定義を書いてくれる感じです. 例えば,インスタンスを何個作ってもその全員が引数に char* 型をとるコンストラクタで構築されるコードを書いたならば,必要なのは ToLL<char> だけなので,これだけが定義されることになります. 誰か一人でも int* ならば,int版も必要になるので ToLL<int> も定義されます. templateを使わずに私が最初から頑張って書くとしたら long long CharToLL( char * ); long long IntToLL( int * ); ...(他にも必要な型のがあればその分だけ) という複数種類の関数のコードを書かなきゃならないところを,コンパイラ様が代わりにやってくれるということですね.
do_a

2024/02/06 01:41

すごい! 確かに今調べていたんですが、変数aとcの型を試しに両方intにした場合、test[0].Cvtとtest[2].Cvtの参照先アドレスが一緒になってました! コンパイラ様賢いですね。 これなら全然問題なさそうです。 とても勉強になりました。ありがとうございました!
fana

2024/02/06 01:52 編集

うお? コメントが変わった模様. > ToLLはstaticなので、インスタンスの数に関わらず定義は一回ですね。 (static かどうかに限らず)同じメソッドとか関数の定義が複数になることは無いハズです.変数じゃないので. > インスタンス毎の消費メモリはCvtの分で済む? そうですね.TEST型インスタンスが保持するデータ(:メンバ変数)というのは val と Cvt の2つのポインタだけです.仮に両ポインタのサイズが64bitだとしたら,「1インスタンスあたり128bitのサイズ」みたいな感じになると思います. > ToLL は staticじゃないとエラーになりましたが、これってどうしてでしたっけ? これは単に私が ToLLf Cvt; //Cvt の型というのは ToLLf using ToLLf = long long(*)( void * ); // TolLLf というのはこういう形の関数ポインタ という形で型を書いているからです. ToLLf というのは「引数が void* で戻り値が long long な **(ふつうの)関数** を指すポインタ」なので,「引数がvoid* で戻り値が long long な **(staticじゃない)メソッド** を指すポインタ」とは型が違います. 関数ポインタ と メンバ関数ポインタ に関して検索するとかすればその辺の話は見つかるかと思います.(メソッドはどのインスタンスについて呼ぶのか?という情報が必要だから実際には引数が1個多い的な雰囲気の話)
do_a

2024/02/06 01:56

>> メソッドはどのインスタンスについて呼ぶのか?という情報が必要だから実際には引数が1個多い的な雰囲気の話 分かりやすいご説明ありがとうございます。 ● static long long ToLL( void *val ) は、テンプレートにより必要な型のバージョン数だけ作られることになる(コンパイラによりオート) ● コンストラクタで ToLL<VAL_T> を引数に取った時に、対応する型バージョンのToLL()関数のアドレスが渡される ● 同じ関数が複数回定義されることはないので、ToLL()関数はオーバーロードの数だけしか存在しない 上記3点、理解しました。お付き合い頂きありがとうございました。
fana

2024/02/06 02:10

どうでもいい話:template に関しての個人的な感覚というか. 例えば私の上司みたいな立場の人が「ここにchar型のデータを処理する関数があるじゃん? このコードをコピペで増やしてint用とlong long用と unsigned short用のやつ作っといて.中身の処理は全く同じでデータの型が違うだけだから.ヨロ.」とか言ってきたら「え? そんな力業で? もっと他にやり方無いのか?」とか思う気がするんだけど, template 書くって,これと全く同じ話を私がコンパイラに言ってるだけなんですよね.「これコピペで増やして対応よろしく^^」って. なんかスマート感(?)みたいなのが無いよなぁ,みたいな? (まぁ結局他に手が無いならば仕方ないのだけど)
SaitoAtsushi

2024/02/06 02:47

言語仕様上の用語で言えば実体化 (instantiation) です。 関数テンプレートは実体化の工程を経て関数になります。 関連する規則は複雑で、簡単に説明できるようなものではないので「実体化」について適当な資料を探してみるのがよろしいかと思います。
do_a

2024/02/06 03:30

みなさんありがとうございます!
dameo

2024/02/06 05:47

質問者様へ C++11以降はおろか、C++03、などかなり古いC++や、テンプレートを除けばCでも同じように書けそうです。ただこの回答を望んでいたのであれば、C++仕様バージョンを明記した方が良かったかもしれません。 ※型を別の場所で自由に追加可能である点など、他の方法に対して優位性はあると思います
guest

0

なお、今回void*には(unsigned )int, (unsigned )long long, charなどの数値型しか入らないとします。(キャスト不可な場合は考えない?)

std::tuple() を使うなど。

c++

1#include <iostream> 2#include <tuple> 3 4using namespace std; 5 6int main(void) { 7 int a = 1; 8 char b = 'c'; 9 long long c = 10000; 10 11 tuple test{a, b, c}; 12 13 apply([](auto&&... i) { 14 ((cout << (long long)i << endl), ...); 15 }, test); 16}

投稿2024/02/05 09:48

編集2024/02/05 09:51
melian

総合スコア19825

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

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

do_a

2024/02/05 23:57

ご回答ありがとうございます。 こんな方法もあるんですね。 色々な角度から解決策が見つかり、とても参考になります。
dameo

2024/02/06 05:30

C++17以降ですね。配列でないと書いておいた方がいいかも。
guest

0

typeがある以上どうしても実行時に条件分岐は必要になりますね。TESTクラスにメソッドを付けてその中で type による分岐をするのがシンプルな策ではないでしょうか。

std::variant を使うと条件分岐を隠すことはできます。

c++

1template <typename T> class Holder { 2public: 3 Holder(T *p) : val(p) {} 4 long long Get() const { return static_cast<long long>(*val); } 5 T *val; 6}; 7 8using TEST = std::variant<Holder<int>, Holder<char>, Holder<long long>>; 9 10int main() { 11 int a = 1; 12 char b = 'c'; 13 long long c = 10000; 14 15 TEST test[3] = {&a, &b, &c}; 16 for (int i = 0; i < 3; i++) { 17 std::visit([](auto &v) { std::cout << v.Get() << std::endl; }, test[i]); 18 } 19 return EXIT_SUCCESS; 20}

投稿2024/02/05 07:41

編集2024/02/05 07:44
int32_t

総合スコア20884

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

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

do_a

2024/02/05 23:53

御回答ありがとうございます。 こちらはvariantとテンプレートを使った方法ですね。 variantを知らなかったので驚きです。まだまだ知見が狭いです。参考にさせて頂きます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問