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

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

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

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

Q&A

解決済

3回答

1777閲覧

constなオブジェクトを構造化束縛したらconstになりますか?

fana

総合スコア11954

C++

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

4グッド

3クリップ

投稿2022/10/07 01:29

編集2022/10/12 06:18

C++初心者です!
「構造化束縛」というのを使ってみようとしているのですが,下記コードの(2)がコンパイルエラーになってしまい,困っています.

A が const だと,a, b も const になってしまうように見えますが,「構造化束縛」とはそういうものなのでしょうか?
そうである場合, const である A に対して a, b が非 const な形にする術はないのでしょうか?

C++

1int main() 2{ 3 //何かデータがあって… 4 const int A[] = { 10, 20 }; 5 6 {//(1) 7 //データの現在の内容を得てそこから計算していきたい 8 auto a = A[0]; 9 auto b = A[1]; 10 //何か計算していく.ごく普通の話だと思う. 11 a += 5; 12 b *= 10; 13 } 14 15 {//(2) 上記(1)と同じことをやろうとしているつもりだが… 16 auto [a,b] = A; //データの現在の内容を得て 17 //そこから計算していきたいのだけど 18 //:コンパイルエラー.原因は「a,b は const だから」とのこと. 19 a += 5; 20 b *= 10; 21 } 22 return 0; 23}

[追記]
念のため,試している環境を示しておきます.

  • (貧乏故,非常に古いPCの上で)Visual Studio 2017 を使用しています.プロジェクトのプロパティで「C++ 言語標準」という選択肢を「ISO C++17標準(/std:c++17)」にしてします.
  • 上記環境が悪いのか? と思い,paiza.io というサイトでもやってみましたが結果は同じでした.(なお,こちらのサイトでは言語選択肢が「C++」となっていて,C++のどの版として実行されているのかはわかりませんでした)

(その後,Visual Studio 2019 も試したところ, どうにも Visual Studio 2017 だけ挙動が異なるようであった → 自己回答の形で記述)


[追記]
ココ を読んでいます.
私には非常に読みづらく,解釈に難儀していますが,この質問の例(配列)については以下のような話なのでしょうか?

"Case 1: binding an array" のところに,

Note that if the array type E is cv-qualified, so is its element type.

という記述があり,コレが関係しているように思えます.
(「E が const なら要素も const になるぞ」という話かと)

ここの type E とは何か? というのは,ちょっと上の方で

We use E to denote the type of the expression e.

とのことです. e の型である,と.

そしたら expression e とは何か? というと,もうちょっと上の方で

A structured binding declaration first introduces a uniquely-named variable (here denoted by e) to hold the value of the initializer, as follows:
・ If expression has array type A and no ref-qualifier is present, then e has type cv A, where cv is the cv-qualifiers in the cv-auto sequence, and each element of e is copy- (for (1)) or direct- (for (2,3)) initialized from the corresponding element of expression.

とのことで,今は配列の場合であって,且つ私の書き方は

attr(optional) cv-auto ref-qualifier(optional) [ identifier-list ] = expression ; (1)

に相当すると思うので,
e とは,私の配列の要素のコピーを持っている配列変数なのであって,この e の型とは cv A だそうです.
cv-auto sequence というところに cv なるものを私は特に書いていないと思うので,つまり e の型たる E とはここでいうところの array type A かと.

で,この array type A というのが,私の例では const int[2] だということ( int[2] ではなくて)…なのかと読みました.
合っていますか?

int32_t, Zuishin, Serbonis, yohhoy👍を押しています

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

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

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

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

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

Zuishin

2022/10/07 01:32

> C++初心者です! ダウト
fana

2022/10/07 02:10

おそらく 「初心者」とは? という認識の違いによるものであろうと思います. (一意な定義がある言葉ではないがために) 私個人の感覚としては, このように「ちょっと未知の言語機能を触れようとしたらコンパイルエラーで手も足も出ません><」という状態になるようでは,「初心者」よりも熟練した感じの何か(例えば「中級者」だとか)を自称するのは無理だと思っています.
dodox86

2022/10/07 02:19

> paiza.io というサイトでもやってみましたが結果は同じでした.(なお,こちらのサイトでは言語選択肢が「C++」となっていて,C++のどの版として実行されているのかはわかりませんでした) paiza.io上でのコンパイル時の細かいオプションが示されていないのでお求めの情報には足りないのかもしれませんが、C++17準拠かなと思われます。 [paiza.IO利用ガイド] https://paiza.io/help?locale=ja-jp より: > C++ C17++ / clang version 10.0.0-4ubuntu1
vann_2921

2022/10/07 03:44

こちらの質問はC++の仕様を確認したいのでしょうか?その場合回答はできる、できない、になると思います。 それともconstなAに対してauto [a,b] = A;(a,bは非const)という書き方が可能なクラスの実装方法が知りたいということでしょうか?
fana

2022/10/07 05:02

両方とも Yes でお願いします. > こちらの質問はC++の仕様を確認したいのでしょうか?その場合回答はできる、できない、になると思います。 まずは,このような場合に「仕様上,必ず aやbに相当する物は const になる」ということなのか否か? を知りたいです. constになるのであれば「このような使い方はできない」のであり,すなわち「こんなコードを書いてる私が悪い」ということなのだとわかるので. > それともconstなAに対してauto [a,b] = A;(a,bは非const)という書き方が可能なクラスの実装方法が知りたいということでしょうか? 仮に,この点に抜け道(?)を持った独自の型を書けるかもしれないという話なのであれば,(そんなのを実用するか否かは別として)その話には興味があります.
fana

2022/10/11 04:32 編集

> これを見るとだめそうです。 示されたリンク先を拝見しましたが, どこを見れば本件(右辺が const オブジェクトである場合,もっと制限するなら,右辺が const な配列である場合)に関しての情報が得られるのか…が私にはわかりませんでした.
Zuishin

2022/10/11 05:12

> 構造化束縛によって決まる型 > クラスのpublicな各メンバ変数を構造化束縛で分解して受け取った時, どのような型になるのでしょうか. > 構造化束縛はautoキーワードを使って宣言するので直感的には, 各メンバ変数をautoで型推論した型になりそうです. > しかし, C++17の規格書相当のN4659の11.5.4項を見てみると, 分解されたそれぞれの変数の型は元のクラスで定義された型になる7と読み取れます. > 正確には, 構造化束縛の際にconstやvolatileや参照修飾子を用いて宣言しているならこれらも反映した型になります. ↩︎ とあります。 > N4659の11.5.4項 の少し前(https://timsong-cpp.github.io/cppwp/n4659/dcl.struct.bind)を見ると、次のようにあります。 > First, a variable with a unique name e is introduced. If the assignment-expression in the initializer has array type A and no ref-qualifier is present, e has type cv A and each element is copy-initialized or direct-initialized from the corresponding element of the assignment-expression as specified by the form of the initializer. ここで cv は const と volatile のことです。
fana

2022/10/11 06:10 編集

> > N4659の11.5.4項 > の少し前… そこの cv に関しては,質問内の[追記]に記した > cv-auto sequence というところに cv なるものを私は特に書いていないと思うので… のように解釈したのですが,これは読み違いでしょうか? つまり,ここの "cv A" の cv とは, const auto [a,b] = 何か; とか書いた場合の左辺側の cv のことであって,右辺が const かどうかという話ではない…,と. (で,右辺が const な配列の場合,型 "A" 自体が「 constな配列」型になるのかな,と)
fana

2022/10/11 06:08 編集

> 分解されたそれぞれの変数の型は元のクラスで定義された型になる7と読み取れます. の話については,メンバ変数自体が const な型である場合(そこの例で言えば, Hoge のメンバ a 自体が const int である)の話であって,(私の自己回答に書いた St 型の例のような)右辺側が const オブジェクトである場合の話ではないように見えます. また,ここの 注釈7 の > 正確には, 構造化束縛の際にconstやvolatileや参照修飾子を用いて宣言しているならこれらも反映した型になります. に関しても,【…用いて宣言しているなら】の文が,「何を」宣言している場合 について述べているのか,というと,左辺側なのではないか? …と.
Zuishin

2022/10/11 06:28

A が右辺なので cv A は右辺のことと読むのが妥当だと思いますが、ここでわざわざ cv というのがついているところで右辺の cv が分解されてもそのまま左辺に適用されると読みました。 実行結果を見てもその解釈で特に矛盾はないように見えます。
guest

回答3

0

それともconstなAに対してauto [a,b] = A;(a,bは非const)という書き方が可能なクラスの実装方法が知りたいということでしょうか?

仮に,この点に抜け道(?)を持った独自の型を書けるかもしれないという話なのであれば,(そんなのを実用するか否かは別として)その話には興味があります.

一番単純な方法はstd::tupleをusingする方法かと思います。

c++

1#include <iostream> 2#include <tuple> 3 4using A = std::tuple<int, int>; 5 6int main() { 7 const A A = { 1, 2 }; 8 auto [a, b] = A; 9 //std::get<0>(A) = 2; 要素の変更はできない 10 a += 1; 11 std::cout << a << " != " << std::get<0>(A) << "\n"; 12}

それ以外の方法として、自作クラスを構造化束縛できるようにする方法があります。
自作クラスに対して std::tuple_size と std::tuple_size を特殊化し、get()をメンバ関数かグローバル関数のいずれかで定義するといいようです。参考

c++

1#include <iostream> 2#include <tuple> 3 4template<size_t N, class T> 5struct MyArray { 6 int operator[](size_t index) const { return arr[index]; } 7 int& operator[](size_t index) { return arr[index]; } 8 T arr[N]; 9 template<size_t N> 10 T get() const { return arr[N]; } 11 template<size_t N> 12 T& get() { return arr[N]; } 13}; 14 15namespace std { 16 17template<size_t N, class T> 18struct tuple_size<MyArray<N, T>> : integral_constant<size_t, N>{}; 19 20template<size_t Index, size_t N, class T> 21struct tuple_element<Index, MyArray<N, T>> { 22 using type = T; 23}; 24 25} 26 27using A = MyArray<2, int>; 28 29int main() { 30 const A A = {1, 2}; 31 auto [a, b] = A; 32 //A[0] = 2; 要素の変更はできない 33 a += 1; 34 std::cout << a << " != " << A[0] << "\n"; 35}

上記の任意の型を構造化束縛に対応させる方法を利用し、配列の型に対して特殊化できればできそうな雰囲気がありますが私にはできませんでした。

投稿2022/10/08 03:41

vann_2921

総合スコア190

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

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

fana

2022/10/11 01:20 編集

struct MyArray 側のコードは, VS2017 だと a が const だということでコンパイルエラーになりました.ダメみたいです. paiza.io だと MyArray::get のテンプレート仮引数Nが MyArray自体のテンプレート仮引数と被るというエラーを修正した(:get側のNを別の名前に変えた)ら動作しました. すなわち,2つの環境で結果が異なる様子.(一体どちらが正しいのか…?)
fana

2022/10/11 01:29

おっと,順番が前後してしまいましたが, tuple の方も結果は同様で, VS2017だとconstになりコンパイルが通らず,paiza.ioだとOKです.
fana

2022/10/11 02:45

私のポンコツPCでギリ動く VS2019 で試したところ, tuple, MyArray 共に const にならずに無事実行できました.
guest

0

ベストアンサー

A が const だと,a, b も const になってしまうように見えますが,「構造化束縛」とはそういうものなのでしょうか?

はい。

より正確には「対象が constな配列型 の場合は、束縛される名前(a, b)もconstとなってしまう」です。下記コードのように対象が constなクラスオブジェクト であれば、構造化束縛により導入される名前a, bは非constな型(int)となります。

c++

1int main() 2{ 3 {// 非const配列 4 int arr[] = { 1, 2 }; 5 auto [a, b] = arr; 6 a += 1; // OK 7 } 8 {// const配列 9 const int arr[] = { 1, 2 }; 10 auto [a, b] = arr; 11 a += 1; // NG 12 } 13 14 struct S { int x, y; }; 15 {// 非constオブジェクト 16 S obj = { 1, 2 }; 17 auto [a, b] = obj; 18 a += 1; // OK 19 } 20 {// constオブジェクト 21 const S obj = { 1, 2 }; 22 auto [a, b] = obj; 23 a += 1; // OK 24 } 25}

const である A に対して a, b が非 const な形にする術はないのでしょうか?

言語組み込みの配列型(const int[N])に対する回避手段は存在しない...と思います。たぶん。

std::array<int, N>型を経由して強引に実現することはできます。
(C++20で追加された配列型からstd::array型へ変換するstd::to_arrayヘルパ関数を使っていますが、C++17でも相当機能を自作可能です。)

c++

1#include <array> 2 3int main() 4{ 5 const int arr[] = { 1, 2 }; 6 auto [a, b] = std::to_array(arr); 7 a += 1; // OK 8}

C++17範囲の実装例: https://wandbox.org/permlink/SnYTx3EQgeSsUkKA

投稿2022/10/07 08:53

編集2022/10/07 09:22
yohhoy

総合スコア6191

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

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

fana

2022/10/11 01:24

struct S { int x, y; }; のようなメンバがpublicなやつであればいける(:aやbがconstにならない)様子ですが,ちょっと理由がよくわからないので勉強中です. メンバが private だったらどうするのか? ということで,別の回答にあるような tuple_size だとかを書く話を試していたのですが,私の環境(VS2017)だとどうしても const になる様子で苦戦中です.環境側の問題なのでしょうか?
fana

2022/10/11 02:44

> メンバが private だったらどうするのか? ということで,別の回答にあるような tuple_size だとかを書く話を試していたのですが,私の環境(VS2017)だとどうしても const になる様子で苦戦中です.環境側の問題なのでしょうか? 私のポンコツPCでギリ動くVS2019で試したところ,VS2017とは違い,const にならずに無事実行できました.
fana

2022/10/11 05:01

> C++17範囲の実装例 C++初心者なので,見たことない物がてんこ盛りで面喰っています. いろいろググって読んでみていますが > return { {a[I]...} }; の > a[I]... のところは,「パラメータパックの展開」とかいうやつでしょうか? すなわち,ここは,シーケンス I の全ての要素についての a[要素] をカンマ区切りで並べた物になる: 要素2個なこの例であれば,returnのところは return { { a[0], a[1] } }; となる, ということで合っていますか? …と,考えると,2重になっている {} は1つでも良いのでは?と思うのですが,2重である意味(意図?)があるのでしょうか?
yohhoy

2022/10/12 05:09 編集

> 「パラメータパックの展開」とかいうやつでしょうか? > returnのところは return { { a[0], a[1] } }; となる, ということで合っていますか? はい。上記の通り展開されます。 > 2重になっている {} は1つでも良いのでは? https://en.cppreference.com/w/cpp/container/array/to_array cppreferenceサイト実装例をそのまま利用したため、深い意図はありませんでした。 関連話題として https://yohhoy.hatenadiary.jp/entry/20131116/p1 があります。 おしゃる通り、C++17では2重にする必然性はありませんね。1重/2重どちらもOK。 (std::array型の定義上は2重{{}}が自然ですが、ユーザ視点だと1重{}の方が自然かなと)
fana

2022/10/12 06:06

本題のみならず,いろいろと学びの機会を与えていただき感謝です.
guest

0

Visual Studio 2017, paiza.io に加えて,Visual Studio 2019 を試してみた(私のポンコツPCにギリ導入できたので)ところ,以下のような結果でした.
(VS2019 でも「C++言語標準」プロパティを「ISO C++17 標準(/std:c++17)」として試した)

C++

1#include <iostream> 2#include <tuple> 3 4namespace 5{ 6 class TestClass 7 { 8 private: 9 int m_int; 10 float m_float; 11 public: 12 TestClass( int i, float f ) : m_int{i}, m_float{f} {} 13 void Show() const { std::cout << m_int << " , " << m_float << std::endl; } 14 15 public: //構造化束縛に必要なやつ 16 template< size_t I > 17 auto get() const 18 { 19 if constexpr (I==0) 20 return m_int; 21 else 22 return m_float; 23 } 24 25 template< size_t I > 26 auto& get() 27 { 28 if constexpr (I==0) 29 return m_int; 30 else 31 return m_float; 32 } 33 }; 34} 35 36namespace std 37{// TestClass の構造化束縛に必要なやつ 38 template<> 39 struct tuple_size<TestClass> : integral_constant<size_t, 2> {}; 40 41 template<> 42 struct tuple_element<0,TestClass>{ using type=int; }; 43 44 template<> 45 struct tuple_element<1,TestClass>{ using type=float; }; 46} 47 48//テスト 49int main() 50{ 51 {//自作クラス TestClass 52 std::cout << "TestClass" << std::endl; 53 {//(const でない) 54 TestClass Obj{ 10, 3.14f }; 55 auto &[i,f] = Obj; 56 i += 5; 57 f *= 2; 58 std::cout << i << " , " << f << std::endl; 59 Obj.Show(); 60 } 61 {//(const) 62 //※ VS2017だと i,f が const になりコンパイルエラー 63 const TestClass Obj{ 10, 3.14f }; 64 auto [i,f] = Obj; 65 i += 5; 66 f *= 2; 67 std::cout << i << " , " << f << std::endl; 68 Obj.Show(); 69 } 70 } 71 72#if 0 73 {//const 配列 : これは a,b が const になりコンパイルエラー 74 std::cout << "const Array" << std::endl; 75 const int A[]{ 4, 9 }; 76 auto [a,b] = A; 77 a += 2; //コンパイルエラー.a は cost int 78 b *= 7; //同上 79 } 80#endif 81 82 {//const tuple 83 //※ VS2017だと i,f が const になりコンパイルエラー 84 std::cout << "const Tuple" << std::endl; 85 const std::tuple< int, float > T{ 1, 1.66f }; 86 auto [i,f] = T; 87 i += 5; 88 f *= 2; 89 std::cout << i << " , " << f << std::endl; 90 std::cout << std::get<0>(T) << " , " << std::get<1>(T) << std::endl; 91 } 92 93 {//const メンバがpublicなやつ 94 struct St{ int i; float f; }; 95 std::cout << "const struct S" << std::endl; 96 const St S = { 10, 10.125f }; 97 auto [i,f] = S; 98 i += 3; 99 f -= 7.74f; 100 std::cout << i << " , " << f << std::endl; 101 std::cout << S.i << " , " << S.f << std::endl; 102 } 103 return 0; 104}
  • const 配列 を構造化束縛すると const になる.なので,質問コードみたいなのは無理.
  • メンバが全部 public な型(St) だとOK(:const オブジェクトに対しても構造化束縛したやつは const にならない).
  • tuple と,tuple みたく扱えるように頑張った型(TestClass) に関しては,VS2017だと const になってしまう(コンパイルエラーになる)が,他はOK.

[追記]

型情報の分岐をお手軽に実装するなら std::conditional_t メタ関数が利用できます。(3分岐以降は面倒...)

という情報を頂いたので,チャレンジ.
「C++ template 可変長 再帰」とかググって文法を見様見真似して四苦八苦しながら書いた.

C++

1// conditional_t というやつを再帰的に頑張れば, 2// 「書き並べた型の指定番目の型」っていうのをやれるんじゃないか? という実験. 3// ググった感じ,こういうのは template の可変長のところを先頭から1個ずつ捨てていく感じで書くっぽいので,それに挑戦. 4// 5// TypeAt< 0, int, float > と書いたら int が, 6// TypeAt< 1, int, float > と書いたら float が using された type になる,というのが目的. 7 8// 9//とりあえず TypeAt< どうのこうの >::type っていうのが存在するよ,っていうのが必要だと思うのでコレを書く. 10template< size_t I, typename ...T > 11struct TypeAt 12{ 13 //じゃあその type ってのは何型なんだよ?っていうと…謎だが.実際上,使わないハズだから何でもいいから書いとけばいいのかな. 14 using type = bool; //※ダミーでてきとーな型を書いた! 15}; 16 17//コレで↑の特殊化になるとかいう謎の文法に理解が追い付かないのだが, 18//何やらテンプレートで再帰を書く場合はこういう形でやるらしい. 19template< size_t I, typename H, typename ...T > 20struct TypeAt< I, H, T... > 21{ 22 using type = std::conditional_t< I==0, H, typename TypeAt< I-1, T... >::type >; 23}; 24 25//---------- 26//↑を使って,TestClass 用の tuple_element を書く 27 28namespace std 29{// TestClass の構造化束縛に必要なやつ 30 31 template<> 32 struct tuple_size<TestClass> : integral_constant<size_t, 2> {}; 33 34#if 0 //個別に書いてたやつ 35 36 template<> 37 struct tuple_element<0,TestClass>{ using type=int; }; 38 39 template<> 40 struct tuple_element<1,TestClass>{ using type=float; }; 41 42#else //TypeAt を使ってみたやつ 43 44 template< size_t I > 45 struct tuple_element<I,TestClass> 46 { 47 //using type = std::conditional_t< I==0, int, float >; //←コレはコメントで教えてもらったやつ 48 using type = typename TypeAt< I, int, float >::type; 49 }; 50 51#endif 52}

投稿2022/10/11 02:54

編集2022/10/12 05:38
fana

総合スコア11954

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

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

fana

2022/10/11 08:58 編集

get の中身みたいに,tuple_element の方も if constexpr というやつで using type=XXX; のところを書けないのか…? と思ったけど,書けないっぽい. 型の定義自体が変わっちゃうような書き方はできない,ということらしい(? つまり if constexpr というのは class XXX{}; の{}内のところには書けないということか)
yohhoy

2022/10/12 03:49

FYI: if constexprも文(statement)の一種ですから、クラス定義中に直接記述することはできません。 https://cpprefjp.github.io/lang/cpp17/if_constexpr.html 型情報の分岐をお手軽に実装するなら std::conditional_t メタ関数が利用できます。(3分岐以降は面倒...) template<size_t I> struct tuple_element<I, TestClass> { using type = std::conditional_t<I==0, int, float>; };
fana

2022/10/12 05:41

ご教示ありがとうございます. > (3分岐以降は面倒...) に関して, using type = std::conditional_t<I==0, int, float>; の float のところにまた conditional_t を… と頑張って入れ子にしていけばいいのかな? というのをやってみました(回答に追記.もはや文法が私にはイミフな世界ですが動いているような雰囲気かも).
fana

2022/10/12 06:15 編集

(MEMO) > using type = typename TypeAt< I, int, float >::type; ここの "typename" を書かなかったことが原因のコンパイルエラーでしばらく苦しんだ.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.37%

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

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

質問する

関連した質問