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

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

新規登録して質問してみよう
ただいま回答率
85.50%
シリアルポート

シリアルポートは一度に一ビットごと移行される物理的なインターフェイスです。一般的には、9ピンのd-subコネクタであるRS-232を指します。

C++

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

mbed

mbed(エンベッド)は、Webサイト上でC++を使って開発を行う、ワンボードマイコンのプロトタイピングツールです。PCに開発環境をインストールする必要がなく、Webにアクセスできればどこにいても開発を行うことができます。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

Q&A

解決済

1回答

6421閲覧

template関数の完全特殊化の引数に参照渡しで配列を渡したところ、Expression must have pointer-to-object typeというエラーが出ます

TakenMaker

総合スコア1

シリアルポート

シリアルポートは一度に一ビットごと移行される物理的なインターフェイスです。一般的には、9ピンのd-subコネクタであるRS-232を指します。

C++

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

mbed

mbed(エンベッド)は、Webサイト上でC++を使って開発を行う、ワンボードマイコンのプロトタイピングツールです。PCに開発環境をインストールする必要がなく、Webにアクセスできればどこにいても開発を行うことができます。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

0グッド

0クリップ

投稿2021/05/13 10:16

編集2021/05/13 15:46

###実現したいこと
完全特殊化のところ(53行目)で元の関数と同じように配列を用いた処理をしたい

発生している問題・エラーメッセージ

エラーメッセージ Error: Expression must have pointer-to-object type in "Serial_Writer.h", Line: 53, Col: 25

オーバーロード元では配列を用いた処理(send[0]など)ができるのに完全特殊化したところで同様の処理を行うと上記のエラーが出ます 。

該当のソースコード

c++

1#ifndef SERIAL_WRITER 2#define SERIAL_WRITER 3#include <mbed.h> 4 5class Serial_Writer 6{ 7public: 8 Serial_Writer(PinName TxPin,PinName RxPin,int baudrate); 9 template <typename T> 10 inline void write(T &send,int delay){ 11 int num=sizeof(send); 12 char buffer[num+2]; 13 for (int i=1,k=0;i<=num;k++){ 14 for(int _bitNum=sizeof(send[0])-1;_bitNum>=0;i++,_bitNum--)buffer[i]=(send[k]>>(8*_bitNum))&0xFF; 15 } 16 buffer[0]='['; 17 buffer[num+1]=']'; 18 for (int p=0;p<sizeof(buffer);p++)_Serial.putc(buffer[p]); 19 wait_ms(delay); 20 } 21 22 template <typename R> 23 inline int receive(R &get){ 24 int num=sizeof(get); 25 int num_0=sizeof(get[0]); 26 char buffer[num+2]; 27 if (_Serial.readable()){ 28 for(int i=0;i<sizeof(buffer);i++){ 29 buffer[i]=_Serial.getc(); 30 if(buffer[0]!='[')return -1; 31 } 32 if(buffer[num+1]==']'){ 33 for (int s=0;s<(num/num_0);s++)get[s]=0x0; 34 for (int p=1,k=0;p<=num;k++){ 35 for (int _byte=num_0-1;_byte>=0;p++,_byte--)get[k]|=buffer[p]<<(8*_byte); 36 } 37 return 0;//正常終了 38 }else return -1;//異常終了1(正しく受信していない) 39 }else return -2;//異常終了2(受信しない) 40 } 41 42private: 43 Serial _Serial; 44}; 45 46template<> 47inline void Serial_Writer::write<double>(double &send,int delay){ 48 int num=sizeof(send); 49 char buffer[num+2]; 50 int send_c[num]; 51 for(int _n=0;_n<num;_n++){ 52 send_c[_n]=int(send[_n]*100);//エラー箇所 53 } 54} 55 56template<> 57inline void Serial_Writer::write<float>(float &send,int delay){ 58 int num=sizeof(send); 59 char buffer[num+2]; 60 int send_c[num]; 61 for(int _n=0;_n<num;_n++)send_c[_n]=int(send[_n]*100); 62} 63 64 65#endif

試したこと

オーバーロードの元の関数では(10行目)では参照渡しで配列を渡し、関数内でsend[0]などを用いてもエラーが出ないことは確認済みです
53行目のところで元の関数と同様の処理を行ってエラーが出る理由がわかりません

補足情報

これはmbedにてシリアル通信のコードを簡潔化するために作ったライブラリです(完全特殊化のところは未完成)
templateを.hファイルでなくて.cppファイルに記述する方法がもしわかる方は教えてください。
ちなみに.cppファイルはこんな感じです

c++

1#include <Serial_Writer.h> 2 3Serial_Writer::Serial_Writer(PinName TxPin,PinName RxPin,int baudrate):_Serial(TxPin,RxPin,baudrate){}

新しいエラー

saitou様のコードを参考に書き換えてみたところ、コンパイルできる寸前で新たなエラーが出てしまったのでお願いします。

c++

1#ifndef SERIAL_WRITER 2#define SERIAL_WRITER 3#include <mbed.h> 4#include <iostream> 5 6class Serial_Writer 7{ 8private: 9 template <typename T,std::size_t N> 10 struct writer{ 11 void write(T (&send)[N],int delay){ 12 int num=sizeof(send); 13 char buffer[num+2]; 14 for (int i=1,k=0;i<=num;k++){ 15 for(int _bitNum=sizeof(send[0])-1;_bitNum>=0;i++,_bitNum--)buffer[i]=(send[k]>>(8*_bitNum))&0xFF; 16 } 17 buffer[0]='['; 18 buffer[num+1]=']'; 19 for (int p=0;p<sizeof(buffer);p++)_Serial.putc(buffer[p]);//エラー発生(Error: A nonstatic member reference must be relative to a specific object in "Serial_Writer.h", Line: 19, Col: 48) 20 wait_ms(delay); 21 } 22 }; 23 24 template<std::size_t N> 25 struct writer<double,N>{ 26 void write(double (&send)[N],int delay){ 27 int num=sizeof(send); 28 char buffer[num+2]; 29 int send_c[num]; 30 for(int _n=0;_n<num;_n++)send_c[_n]=int(send[_n]*100); 31 } 32 }; 33 34 template<std::size_t N> 35 struct writer<float,N>{ 36 void write(float (&send)[N],int delay){ 37 int num=sizeof(send); 38 char buffer[num+2]; 39 int send_c[num]; 40 for(int _n=0;_n<num;_n++)send_c[_n]=int(send[_n]*100); 41 } 42 }; 43 44 template <typename R> 45 inline int receive(R &get){ 46 int num=sizeof(get); 47 int num_0=sizeof(get[0]); 48 char buffer[num+2]; 49 if (_Serial.readable()){ 50 for(int i=0;i<sizeof(buffer);i++){ 51 buffer[i]=_Serial.getc(); 52 if(buffer[0]!='[')return -1; 53 } 54 if(buffer[num+1]==']'){ 55 for (int s=0;s<(num/num_0);s++)get[s]=0x0; 56 for (int p=1,k=0;p<=num;k++){ 57 for (int _byte=num_0-1;_byte>=0;p++,_byte--)get[k]|=buffer[p]<<(8*_byte); 58 } 59 return 0;//正常終了 60 }else return -1;//異常終了1(正しく受信していない) 61 }else return -2;//異常終了2(受信しない) 62 } 63 64public: 65 Serial_Writer(PinName TxPin,PinName RxPin,int baudrate); 66 template<typename T,std::size_t N>void write(T (&send)[N],int delay){ 67 struct writer<T,N> k; 68 k.write(send,delay);//ここでも同様のエラーがでましたがインスタンスを生成すると治りました 69 } 70 Serial _Serial; 71}; 72 73 74#endif

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

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

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

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

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

guest

回答1

0

ベストアンサー

senddouble 型だからです。 インデクスを付けることはできません。 double[] (double の配列) を意図していたのでしょうか? 単純ミスですね。

テンプレートの内部では Two-phase name lookup という仕組みが働きます。 定義時点でその名前が意味するところが確定しているものは解決しますがそうでないものはわかるまで後回しにするという仕組みです。 テンプレートの定義時点でエラーであることがわかるものもありますが、使われてはじめてエラーになることもあります。

T が何者であるかというのはこの write が使われるまでわかりません。 配列が渡されれば問題なく実体化できる可能性があるからこの時点ではエラーになりませんが、配列でないものが渡されればエラーになるでしょう。 そして特殊化したほうについては使われるのを待たずともそれが間違っていることが確定しています。

templateを.hファイルでなくて.cppファイルに記述する方法

無いです。 .cpp の中に記述したらそのファイル内でしか使えません。

古い仕様ではオプショナルな仕様として export キーワードがありましたが、実装した処理系がごく一部しかなかったこともあって C++11 で完全に廃止されました。

参考例

肝心なことは

  • 配列大きさは型の一部。 この場合は省略できない。
  • メンバ関数の部分特殊化はできない。 補助的なクラステンプレートを作って部分特殊化をするのが典型的な方法。

です。

mbed の開発環境を用意していないのでとりあえず雰囲気だけですが、型によって呼び分ける例を書いてみました。 与えられる型によって呼び分けられていることはわかると思います。

cpp

1#include <iostream> 2 3class serial_writer { 4private: 5 template<class T, std::size_t N> 6 struct helper { 7 static void write(T (&send)[N]) { 8 std::cout << "write(generic version)" << std::endl; 9 } 10 }; 11 template<std::size_t N> 12 struct helper<double, N> { 13 static void write(double (&send)[N]) { 14 std::cout << "write(double version)" << std::endl; 15 } 16 }; 17 template<std::size_t N> 18 struct helper<float, N> { 19 static void write(float (&send)[N]) { 20 std::cout << "write(float version)" << std::endl; 21 } 22 }; 23 24public: 25 template<class T, std::size_t N> void write(T (&send)[N]) { 26 helper<T, N>::write(send); 27 } 28}; 29 30int main(void) { 31 int foo[10]; 32 double bar[20]; 33 float baz[30]; 34 35 serial_writer writer; 36 writer.write(foo); 37 writer.write(bar); 38 writer.write(baz); 39}

投稿2021/05/13 10:56

編集2021/05/13 13:52
SaitoAtsushi

総合スコア5437

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

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

TakenMaker

2021/05/13 11:43

sendについて、もう少し詳しい説明をいただきたいです double[] &sendとdouble &send[]どちらも試しましたが Array of reference is not allowed in "Serial_Writer.h", Line: 48, Col: 51 とエラーが出てしまいます....
SaitoAtsushi

2021/05/13 11:55

それは配列の大きさがないからです。 (不完全型) テンプレート引数として整数をひとつ受け取り (ここでは N とします) 仮引数の書き方は double (&send)[N] なのですが…… メンバ関数は部分特殊化を出来ないので SFINAE を活用しないといけないですね。 割と回りくどいメタプログラミングが必要になります。
SaitoAtsushi

2021/05/13 13:53

呼び分ける例を書いてみました。 この場合はクラステンプレートの部分特殊化でなんとかなると思います。
TakenMaker

2021/05/13 15:40

素晴らしいコードありがとうございます もう少しで見えてきそうです。 上のコードを参考に、書き換えてみました。 コンパイルしたところ、19行目のところで"A nonstatic member reference must be relative to a specific object"とエラーが出ます これさえ対処すれば解決できそうなので、お願いします。 もとの質問文の下にコードを追加しておきます。
SaitoAtsushi

2021/05/13 17:12

k を writer<T,N> 型の変数として定義しているわけですね。 その writer<T,N>::write というメンバ関数内でアクセスしようとしている _Serial は何者でしょう? クラス内で定義されるクラス (この場合は writer) は外側のクラス (Serial_Writer) のスコープの内側にあるので、静的 (static 指定のついている) なメンバについては名前空間 (クラス名) で修飾することなくアクセスすることは可能です。 ですが、 writer<T,N> 型のインスタンスと Serial_Writer 型のインスタンスは関係がないのです。 _Serial という名前を持ち出されてもどの Serial_Writer 型のオブジェクト内の _Serial のことを言っているのかわかりません。 具体的な Serial_Writer 型のオブジェクト経由で _Serial にアクセスして欲しいというのがそのエラーが言っていることです。 なので writer<T,N>::write にひとつ引数を増やして陽に _Serial (の参照) を渡すのが手っ取り早い方法です。 そして writer のインスタンスを作るのは意味がありませんから、 writer<T,N>::write は static の指定を付けるのが常道です。 ところでここで話は変わりますが sizeof はバイト数を得るものであって配列の要素数ではないのでたぶん意図とは違うことになっていると思います。 配列の要素数を超えてアクセスしていることになるので未定義の挙動ですね。 修正後の例では配列の大きさは N として導出されていますからそれを使うのがよろしいでしょう。 配列を宣言するにあたって要素数は定数式 (雑に言えばコンパイル時に確定する値) でなければならないというのが C++ のルールなので (const や constexpr の指定がない) 変数 num を介しても許容されるのは GCC や Clang の拡張です。 処理系を変えない前提であれば処理系の拡張を使うのが悪いわけではありませんが、簡単に避けられるものなら避けたほうが好ましいようには思います。
TakenMaker

2021/05/13 22:52

コンパイル、通りました....! 要素数を定数式にするという説明も理解しました。 本当にありがとうございました! 色々勉強になりました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問