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

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

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

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

Q&A

解決済

1回答

2771閲覧

親クラスのメンバ変数(参照)を初期化できない

退会済みユーザー

退会済みユーザー

総合スコア0

C++

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

0グッド

0クリップ

投稿2020/08/09 01:00

編集2020/08/09 22:10

質問内容

下記コードはメンバに

  • 配列or動的配列への参照
  • 配列のサイズを記すメモリへの参照
  • 内部カウンタ変数(参照ではない)

を所持し、引数付きコンストラクタでそれら変数を初期化する
イテレータを作成しようとしたコードです。

登場するクラスは

クラス名備考
BaseIterator主にカウンタ変数にまつわる処理が記された基底クラス
Iterator主に要素書き換えられるようにを参照で返す処理の記された派生クラス
ConstIterator主に要素書き換えられないようにコピーで返す処理の記された派生クラス

より構成されております。

しかし派生クラスの引数付きコンストラクタでコンパイルエラーが発生します。

エラー文(下記記載)を読む限りだと
派生クラスのコンストラクタでメンバ変数element_(参照)を操作できないと言っているようですが
対処法がわかりません。

どのように書けばメンバ変数element_をコンストラクタの引数で初期化できるのでしょうか

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

c++

1#include <iostream> 2#include <memory> 3#include <cassert> 4 5/////////////////////////////////////////// 6// 7// Iterator基底クラス(抽象クラスではない) 8// 9/////////////////////////////////////////// 10template<typename Type, typename SizeType> 11class BaseIterator{ 12 protected: 13 Type& element_;// 配列の参照 14 SizeType& size_;// サイズの参照 15 SizeType counter_ = SizeType(0);// カウンタ 16 17 public: 18 BaseIterator()noexcept = delete; 19 virtual ~BaseIterator() = default; 20 21 auto operator++(int){// 後置++operator 22 this->counter_++; 23 assert(this->counter_ <= this->size_); 24 return *this; 25 } 26 27 decltype(auto) operator++(){// 前置++operator 28 ++this->counter_; 29 assert(this->counter_ <= this->size_); 30 return *this; 31 } 32 33 auto operator--(int){// 後置--operator 34 this->counter_--; 35 return *this; 36 } 37 38 decltype(auto) operator--(){// 前置--operator 39 --this->counter_; 40 return *this; 41 } 42 43 decltype(auto) operator=(const SizeType index)noexcept{// カウンターに数値歳入 44 assert(index <= this->size_); 45 this->counter_ = index; 46 return *this; 47 } 48 49 // 50 // ここから比較関連の処理 51 // 52 auto operator==(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ == itr.counter_;} 53 auto operator!=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ != itr.counter_;} 54 auto operator>(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ > itr.counter_;} 55 auto operator<(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ < itr.counter_;} 56 auto operator>=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ >= itr.counter_;} 57 auto operator<=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ <= itr.counter_;} 58 59}; 60 61/////////////////////////////// 62// 63// Iteratorクラス(参照を返す) 64// 65/////////////////////////////// 66template<typename Type, typename SizeType> 67struct Iterator final:public BaseIterator<Type, SizeType>{ 68 //↓ここでコンパイルエラー発生 69 Iterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){} 70 71 Iterator()noexcept = delete; 72 ~Iterator() = default; 73 74 // カウンタの指す要素の参照を返す 75 decltype(auto) operator*(){return this->element_[this->counter_];} 76 77 // 指定した要素の参照を返す 78 decltype(auto) operator[](const SizeType size){ 79 assert(size <= this->size_); 80 return this->element_[size]; 81 } 82}; 83 84/////////////////////////////////////////////////// 85// 86// ConstIteratorクラス(コピーされた別メモリを返す) 87// 88/////////////////////////////////////////////////// 89template<typename Type, typename SizeType> 90struct ConstIterator final:public BaseIterator<Type, SizeType>{ 91 //↓ここでコンパイルエラー発生 92 ConstIterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){} 93 94 ConstIterator()noexcept = delete; 95 ~ConstIterator() = default; 96 97 // カウンタの指す要素のコピーを返す 98 auto operator*()const {return this->element_[this->counter_];} 99 100 // 指定した要素のコピーを返す 101 auto operator[](const SizeType size)const { 102 assert(size <= this->size_); 103 return this->element_[size]; 104 } 105}; 106 107/////////////// 108// 109// main関数 110// 111////////////// 112int main(){ 113 114 auto size = size_t(5); 115 auto mem = std::make_unique<int[]>(size); 116 117 auto itr = ConstIterator(mem, size); 118 119 return 0; 120} 121

エラー文

terminal

1[コードのパス]/main.cpp:90:88: error: typename specifier refers to non-type member 'element_' in 'BaseIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>' 2 ConstIterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){}// ここでコンパイルエラー発生 3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~ 4[コードのパス]/main.cpp:114:16: note: in instantiation of member function 'ConstIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>::ConstIterator' requested here 5 auto itr = ConstIterator(mem, size); 6 ^ 7[コードのパス]/main.cpp:14:15: note: referenced member 'element_' is declared here 8 Type& element_;// 配列の参照 9 ^ 10[コードのパス]/main.cpp:90:88: error: 'element_' is a protected member of 'BaseIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>' 11 ConstIterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){}// ここでコンパイルエラー発生 12 ^ 13[コードのパス]/main.cpp:14:15: note: must name member using the type of the current context 'ConstIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>' 14 Type& element_;// 配列の参照 15 ^ 16[コードのパス]/main.cpp:90:136: error: typename specifier refers to non-type member 'size_' in 'BaseIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>' 17 ConstIterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){}// ここでコンパイルエラー発生 18 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~ 19[コードのパス]/main.cpp:15:19: note: referenced member 'size_' is declared here 20 SizeType& size_;// サイズの参照 21 ^ 22[コードのパス]/main.cpp:90:136: error: 'size_' is a protected member of 'BaseIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>' 23 ConstIterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){}// ここでコンパイルエラー発生 24 ^ 25[コードのパス]/main.cpp:15:19: note: must name member using the type of the current context 'ConstIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>' 26 SizeType& size_;// サイズの参照 27

開発環境の備考

上記コードはすべてc++20でコンパイルするものとする。

ツールの種類ツールの名前バージョン
コンパイラclang++10.0.0
OSLinux Mint20.0

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

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

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

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

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

guest

回答1

0

ベストアンサー

そもそも移譲コンストラクタは、同じクラス内の別のコンストラクタを呼び出す機能ですから、それは移譲コンストラクタではありません。
移譲コンストラクタ - cpprefjp C++日本語リファレンス

で、そもそも基底クラスにprotectedなものがある設計自体とても汚いと感じるわけですが、
そのprotectedいらなくない? - Qiita
まあそこは元のコードを尊重するということで放置して、やるならこうじゃないですかね?

cpp

1#include <iostream> 2#include <memory> 3#include <cassert> 4 5/////////////////////////////////////////// 6// 7// Iterator基底クラス(抽象クラスではない) 8// 9/////////////////////////////////////////// 10template<typename Type, typename SizeType> 11class BaseIterator{ 12 protected: 13 Type& element_;// 配列の参照 14 SizeType& size_;// サイズの参照 15 SizeType counter_ = SizeType(0);// カウンタ 16 BaseIterator(Type& element, SizeType& size) noexcept : element_(element), size_(size){} 17 public: 18 BaseIterator() noexcept = delete; 19 virtual ~BaseIterator() = default; 20 21 auto operator++(int){// 後置++operator 22 this->counter_++; 23 assert(this->counter_ <= this->size_); 24 return *this; 25 } 26 27 decltype(auto) operator++(){// 前置++operator 28 ++this->counter_; 29 assert(this->counter_ <= this->size_); 30 return *this; 31 } 32 33 auto operator--(int){// 後置--operator 34 this->counter_--; 35 return *this; 36 } 37 38 decltype(auto) operator--(){// 前置--operator 39 --this->counter_; 40 return *this; 41 } 42 43 decltype(auto) operator=(const SizeType index)noexcept{// カウンターに数値歳入 44 assert(index <= this->size_); 45 this->counter_ = index; 46 return *this; 47 } 48 49 // 50 // ここから比較関連の処理 51 // 52 auto operator==(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ == itr.counter_;} 53 auto operator!=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ != itr.counter_;} 54 auto operator>(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ > itr.counter_;} 55 auto operator<(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ < itr.counter_;} 56 auto operator>=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ >= itr.counter_;} 57 auto operator<=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ <= itr.counter_;} 58 59}; 60 61/////////////////////////////// 62// 63// Iteratorクラス(参照を返す) 64// 65/////////////////////////////// 66template<typename Type, typename SizeType> 67struct Iterator final:public BaseIterator<Type, SizeType>{ 68 //↓ここでコンパイルエラー発生 69 Iterator(Type& element, SizeType& size) noexcept : BaseIterator<Type, SizeType>(element, size) {} 70 71 Iterator()noexcept = delete; 72 ~Iterator() = default; 73 74 // カウンタの指す要素の参照を返す 75 decltype(auto) operator*(){return this->element_[this->counter_];} 76 77 // 指定した要素の参照を返す 78 decltype(auto) operator[](const SizeType size){ 79 assert(size <= this->size_); 80 return this->element_[size]; 81 } 82}; 83 84/////////////////////////////////////////////////// 85// 86// ConstIteratorクラス(コピーされた別メモリを返す) 87// 88/////////////////////////////////////////////////// 89template<typename Type, typename SizeType> 90struct ConstIterator final:public BaseIterator<Type, SizeType>{ 91 //↓ここでコンパイルエラー発生 92 ConstIterator(Type& element, SizeType& size) noexcept : BaseIterator<Type, SizeType>(element, size) {} 93 94 ConstIterator()noexcept = delete; 95 ~ConstIterator() = default; 96 97 // カウンタの指す要素のコピーを返す 98 auto operator*()const {return this->element_[this->counter_];} 99 100 // 指定した要素のコピーを返す 101 auto operator[](const SizeType size)const { 102 assert(size <= this->size_); 103 return this->element_[size]; 104 } 105}; 106 107/////////////// 108// 109// main関数 110// 111////////////// 112int main(){ 113 114 auto size = size_t(5); 115 auto mem = std::make_unique<int[]>(size); 116 117 auto itr = ConstIterator(mem, size); 118 119 return 0; 120}

https://wandbox.org/permlink/IJJvIQtlluedZIRj

ちなみにC++20を使えるなら三方比較演算子を使うと楽になると思います。


追記

なぜ派生クラスのコンストラクタのメンバー初期化リスト(mem-initializer-list)で基底クラスのメンバー変数を初期化できないか

mem-initializer-listとは

まずはC++規格書の定義を見てみましょう。

class.base.init/1

In the definition of a constructor for a class, initializers for direct and virtual base class subobjects and non-static data members can be specified by a ctor-initializer, which has the form

ctor-initializer: : mem-initializer-list mem-initializer-list: mem-initializer ...opt mem-initializer-list , mem-initializer ...opt mem-initializer: mem-initializer-id ( expression-listopt ) mem-initializer-id braced-init-list mem-initializer-id: class-or-decltype identifier

英文は読まずともまあ十分わかりやすいかと思いますが、例を見ながら確認しましょう。

cpp

1struct foo { 2 int bar; 3 int hoge; 4 foo() : bar(3), hoge(7) {} 5};

上記で言うbar(3), hoge(7)mem-initializer-listです。またbar(3)hoge(7)mem-initializerです。そしてbarhogemem-initializer-idです。

有効なmem-initializer-idとは

またしても規格書を見ていきます。

class.base.init/2

In a mem-initializer-id an initial unqualified identifier is looked up in the scope of the constructor's class and, if not found in that scope, it is looked up in the scope containing the constructor's definition.

[ Note: If the constructor's class contains a member with the same name as a direct or virtual base class of the class, a mem-initializer-id naming the member or base class and composed of a single identifier refers to the class member.
A mem-initializer-id for the hidden base class may be specified using a qualified name.
— end note
]
Unless the mem-initializer-id names the constructor's class, a non-static data member of the constructor's class, or a direct or virtual base of that class, the mem-initializer is ill-formed.

とくに太字で強調したUnless以降が大事なのでまとめてみます。これによれば、有効なmem-initializer-idとは次の3つです。

  • コンストラクタを書いているクラス自身の名前: 移譲コンストラクタのこと
  • コンストラクタを書いているクラス自身のstatic指定されていないメンバー変数の名前
  • 基底クラス(仮想基底クラスを含む)の名前

これ以外を指定した場合、ill-formedである、と書かれています。

これはどういうことかと言うと、メンバー初期化リストの時点では基底クラスは初期化されていないためアクセスできないということだと考えられます。

以上の内容はbase class protected member init directでググって一番上に出てきた
inheritance - Initialize parent's protected members with initialization list (C++) - Stack Overflow
を元に書かれています。

結論

基底クラスのメンバー変数は有効なmem-initializer-idではないのでill-formed

protectedメンバーを使うことは妥当か

しかしそれでは各Iteratorにカウンタ操作を指定するoperatorを記載せねばならず

コードのサイズが増えてしまうように感じるのですが
それでも依存関係の簡略化を優先したほうが良いのでしょうか。

C++のイテレータを作るのがとっても面倒なのはものすごくよくわかります。

実際にSTLの実装を覗いてみることにします。

msvcの実装

https://github.com/microsoft/STL/blob/master/stl/inc/vector
https://github.com/microsoft/STL/blob/master/stl/inc/xmemory

これは_Vector_iterator_Vector_const_iterator_Iterator_baseという継承になっていて、operator類はそれぞれ定義しているのがわかります。

libstdc++の実装

https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/stl_vector.h
https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/include/bits/stl_iterator.h

イテレータに関しては極めてシンプルで、iteratorとconst_iteratorは同じ__normal_iteratorクラスでできています。protectedメンバーがありますが、std::vectorから使う文についてはprotectedである必要を見いだせないので多分別の用途で使ってるのでしょう。

結論

言い出しておいてなんですが正直わからんです。自分はC++のイテレータに関してはKISSの法則を投げ捨てたほうがうまくいく気がします。比較系は先述の通り三方比較演算子のおかげで自動定義がある程度使えますが、その他は愚直に書くのがなんだかんだいいのかなと思っています。依存名か非依存名かでうっかり引っかかったりする心配もないですし。

本件とは別の問題点からになりますが、これまでイテレータを作るときに継承するべきとされていたstd::iteratorはC++17でdeprecatedになりました。まあそんなこんなであんまり継承したくない心情です。
P0174R2 Deprecating Vestigial Library Parts in C++17


追記2

私の勝手な憶測なのですが
もしかして

規格1:
その規格で必要になる規格1
その規格で必要になる規格2

といった書き方がされているのでしょうか。

違います。構文を定義する文法です。C++に限らず広く用いられる記法です。
まず定義したい構文の名称を書き、それを構成する構文や記号などの並びをその下に字下げして書きます。
例えば識別子(identifier)の定義は
https://timsong-cpp.github.io/cppwp/n4861/lex.name#nt:identifier
にあるように表されます。

identifier: identifier-nondigit identifier identifier-nondigit identifier digit

これはつまり、identifierという構文は、その下の各行のいずれかのことである、という定義です。じゃあidentifier-nondigitってなに?と思って調べるとこれは

identifier-nondigit: nondigit universal-character-name

のように定義されています。じゃあnondigitは?というと

nondigit: one of a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _

と定義されています。

ちょっと戻ってもう一度これを見てください。

identifier: identifier-nondigit identifier identifier-nondigit identifier digit

identifier identifier-nondigitってどういうこと?と思うかもしれません。なんでidentifierの定義にidentifierが出てくるねん!ってなりますよね。これはつまりは再帰的定義をしているわけです。

例えばanondigitであり、identifier-nondigitでもあり、identifierでもあります。

ではabはどうでしょうか?まずbは同様にしてidentifier-nondigitであると言えます。aidentifierであることはすでに述べたとおりです。したがってidentifier identifier-nondigitの定義に当てはまり、abidentifierだと言えます(定義文はスペースで区切られていますが、このスペースは定義文に含まれる構文用語を識別しやすくするためのものであってスペースを入れろという意味ではありません。つまりa bではなくabということです。定義にスペースを入れてほしい場合は明示的にspaceみたいな構文用語を記述するはずです)。

投稿2020/08/09 04:22

編集2020/08/12 12:00
yumetodo

総合スコア5850

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

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

退会済みユーザー

退会済みユーザー

2020/08/09 22:26 編集

yumetodoさん 御回答有難うございます。 > それは移譲コンストラクタではありません。 御指摘ありがとうございます。 タイトルや質問文のおかしな点を修正しておきます。 それと記載して頂いたコードを拝見しました。 基底クラスに引数付きコンストラクタを追加して、 それを派生クラスのコンストラクタの初期化子から 呼びだしていらっしゃるようですが なぜ基底クラスのコンストラクタは そこから呼び出せるのに 基底クラスのprotectedメンバ変数(element_やsize_)が 初期化できなかったのかがまだわかりません。 教えていただけたら幸いです。 あと > 基底クラスにprotectedなものがある設計自体とても汚い の部分のリンク拝見しました。 今回のコードでは Iteratorクラスと ConstIteratorクラスの カウンタ操作を基底クラスに記すことで 同じ処理を二度書かずにすむようにするのが狙いであるのですが、 上記リンク先のサイトに記されているように 依存関係の簡略化(カプセル化の強さ)を優先したいならば BaseIteratorクラスではなくカウンタ操作を記すCounterクラスを 作りそのオブジェクトを各Iteratorにprivate変数として コンポジションすれば良いように感じます。 しかしそれでは各Iteratorにカウンタ操作を指定するoperatorを記載せねばならず コードのサイズが増えてしまうように感じるのですが それでも依存関係の簡略化を優先したほうが良いのでしょうか。 御返事どうかお待ちしております。
退会済みユーザー

退会済みユーザー

2020/08/11 07:17 編集

yumetodoさん 突然失礼します。 質問してから結構時間が経っていて 質問内容自体は解決したので 問題解決とさせて頂きますが 上記疑問点についてお答えいただけたら幸いです。 御返事どうかお待ちしております。
yumetodo

2020/08/11 07:41

せっかくなのでちゃんと規格書を引っ張って正しい説明をしようと思っているのですが、今夜辺りやろうと思って先送りしてました
退会済みユーザー

退会済みユーザー

2020/08/11 08:40

急かしてしまったようで 申し訳ないです。
yumetodo

2020/08/11 15:56

書いてみました。まあ正直私がC++の継承をあんまり好きになれないというか、そんな気持ちもあるような気がします。
退会済みユーザー

退会済みユーザー

2020/08/12 09:26 編集

yumetodoさん 御返事誠にありがとうございます。 規格書まで調べて頂き大変ありがたく 感じております。 しかし規格書のclass.base.init/1に書いてある ctor-initializer: : mem-initializer-list mem-initializer-list: mem-initializer ...opt mem-initializer-list , mem-initializer ...opt mem-initializer: mem-initializer-id ( expression-listopt ) mem-initializer-id braced-init-list mem-initializer-id: class-or-decltype identifier の部分の読み方が私の知識ではわかりません。 私の勝手な憶測なのですが もしかして 規格1: その規格で必要になる規格1 その規格で必要になる規格2 といった書き方がされているのでしょうか。 非常に初歩的な質問かと思いますが 教えていただけたら幸いです。 御返事どうかお待ちしております
yumetodo

2020/08/12 12:01

ついに解答欄の1万文字制限に達したので残りをここに書きます。 他の言語だと、例えばECMAScript(JavaScript)の規格書で、コメントを表す構文の定義は [https://www.ecma-international.org/ecma-262/#sec-comments](https://www.ecma-international.org/ecma-262/#sec-comments) にあるように表されます(注意: ページのロードに極めて時間がかかります) 別の種類の構文定義記法としてはBNFがあります。というかこっちのほうが有名かな。 [バッカス・ナウア記法 - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%82%AB%E3%82%B9%E3%83%BB%E3%83%8A%E3%82%A6%E3%82%A2%E8%A8%98%E6%B3%95)
yumetodo

2020/08/12 12:02

そろそろ本題からそれてきているのに加えて、解答欄に余白がもうないので、これ以上は別途質問を立ててください。こちらからはフォローしておきましたのでそちらから私をフォローすれば回答リクエスト機能で私に通知が来るようにできると思います。あるいは新しい質問のURLをここに貼っても通知が来ます。
退会済みユーザー

退会済みユーザー

2020/08/13 04:00 編集

yumetodoさん 御返事誠にありがとうございます。 御回答拝見いたしました。 しかしとても高度な内容に見えるので 時間をかけて読んでいくことにします。 私の方でも回答依頼ができるよう フォローさせて頂きますので ご質問したときは どうかよろしく御願い致します。 あと元コードの改良版 を考えていたのでwandboxのリンク貼っておきます。 https://wandbox.org/permlink/xQRYln8PX9eN8Rg1 いろいろ考えてみましたがprotectedは消せなかったです しかし三方比較演算子の便利さは実感できました。 これから積極的に使っていこうと思います。 今回とても親身に取り合っていただけ 大変光栄に感じております。 ご回答頂き大変有難うございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問