ArduinoはC++っぽく書けるので扱いやすいですが、ふとしたところで違いを実感します。
今回ちょっとつまずいたのでご紹介します。
サンプルがクソ簡単ですが、別に普段からこんなコード書いてるわけじゃないからね!
###環境
(2019/12/1追記)環境はこんな感じです
- Linux Mint 19.1 (Ubuntu 18.04 LTS)
- Arduino 2:1.0.5+dfsg2-4.1
- avr-g++ (GCC) 5.4.0
#Arduinoだってclassは書ける
Arduinoは「Arduino言語」とよばれるC/C++風の言語でコーディングします。
C++で書けることは大抵書けてしまい、classやnamespaceも使えます。
namespace Space{
int var=1;
}
class Hoge{
private:
int fuga;
public:
Hoge(int _fuga){
fuga=_fuga;
}
};
Hoge hoge=Hoge(Space::var);
こんなおもちゃみたいなマイコンボードを使うのにOOP?って感じですが、ちょっと遊ぶくらいなら必要ないにしても、
ガンガン使用して保守し続けるような(僕の部活がそうです)特殊な環境なら重宝します。
#typedefで楽がしたかった(過去)
諸事情によりちょっと関数を渡したり関数の配列を作ったりしたいと思い、何とかして関数ポインタを返す関数を書けないかと試行錯誤してました。
例えば「引数(bool)をとり、『引数(float)、戻り値floatの関数』のポインタを返す関数returnFP」を定義しようとすると、普通は以下のようになります。
float (*returnFP(bool))(float);
クッソ面倒くさいわけですね。
そこでtypedefを使うと結構スッキリ書けます。一応補足するとtypedefはC/C++の予約語で、型に新しい別名(alias)をつけるためのキーワードです。
typedef float (*myFunc)(float);
myFunc returnFP(bool);
2行にはなりましたが、typedefしたことで『引数(float)、戻り値floatの関数』を指すmyFunc型が定義され、
以降これを使うことで長ったらしく書く必要がなくなるわけです。関数ポインタの配列だって簡潔に書けちゃいます。
何の意味もありませんが、それぞれ引数を2乗・3乗して返す関数を使った例です。
typedef float (*myFunc)(float);
float square(float var){
return var*var;
}
float cube(float var){
return var*var*var;
}
myFunc returnFP(bool flag){
if(flag){
return square;
}else{
return cube;
}
}
#コンパイルが通らない
setupとloopを取って付けて、いざコンパイル!
typedef float (*myFunc)(float);
float square(float var){
return var*var;
}
float cube(float var){
return var*var*var;
}
myFunc returnFP(bool flag){
if(flag){
return square;
}else{
return cube;
}
}
void setup(){}
void loop(){
myFunc f=returnFP(true);
}
コンパイル結果
test.ino:4:1: エラー: ‘myFunc’ does not name a type
たった20行ぽっちのこの内容でもダメ出しを食らってしまいました。
「myFuncは型の名前じゃないよ〜」とほざく上に、その4行目といえばreturn var*2;
しかありません。
でもsetupとloopだけ書き換えてCにしてみるとちゃんとコンパイルは通り、結果の出力も上手くいきます。
なんで?やっぱArduinoとCは別物だからダメなの?
#プロトタイプ宣言の自動挿入
原因はArduino IDEにありました。
Arduino IDEはArduinoの公式統合開発環境であり、エディタで書いたコードをそのまま
コンパイル・書き込みすることが可能です(当然コード自体は外部エディタで編集することも可能です)。
非公式にinoなどのCLIツールがあったりしますが、事実上Arduino IDEが唯一の開発環境と言っていいでしょう。
Arduino IDEは関数のプロトタイプ宣言を自動で挿入します。これにより関数の宣言や定義の順番を気にする必要がなくなり、プログラマでない子供やアーティストなどでも使いやすいようにされています。
この挿入のタイミングはプリプロセッサ命令(#includeとか#defineとか)の直後です。だからtypedefの前にmyFunc returnFP(bool)
の宣言がされてしまい、myFuncが無いとエラーが出たり行の場所が変だったりしたんですね。
#解決策
##自分で宣言を書く
既にプロトタイプ宣言がされている場合はIDEが挿入を行わないらしいです。よっしゃ、これで勝つる!
typedef float (*myFunc)(float);
myFunc returnFP(bool);
//以下省略
...はずなのですが、自分の環境だと何故か上手くいきませんでした。
仕方ないので、もし上手くいった方がいれば教えてください。
##ヘッダに書く
自動挿入のタイミングはプリプロセッサ命令の直後です。ということはヘッダにtypedef書いて挿入すればいいんじゃね?
#pragma once
typedef float (*myFunc)(float);
#include "header.h"
//この辺に宣言が自動挿入されるはず
float square(float var){
return var*var;
}
float cube(float var){
return var*var*var;
}
myFunc returnFP(bool flag){
if(flag){
return square;
}else{
return cube;
}
}
void setup(){}
void loop(){
myFunc f=returnFP(true);
}
結論から言うとこれで上手く動きました。しかし正直たったこれだけのためにヘッダに分けるのは面倒臭い...
でも調べるのはもっと面倒なので、時間がある時にまた検証します。
誰か知ってたら教えてね!