なまもの備忘録

気になったことをつらつらと書いていきます

group_collection_selectメソッドでのエラー[Ruby on Rails 5 アプリケーションプログラミング]

grouped_collection_selectメソッドでのエラー

最近「Ruby on rails 5 アプリケーションプログラミング」の本を読みながらrailsの勉強をしているのだが、本の記述と配布されているサンプルコード(2017/10/29時点)に誤りがあり、割と長い時間嵌ってしまった。 同じ問題に突き当たる人がいないとも限らないので、解決方法を置いて置く。

問題は「4.1.9 選択ボックスの選択をグループ化するーgrouped_collection_selectメソッド」で解説されているサンプルコード

<%= form_for(@review) do |f| %>
    レビュー対象書籍:
    <%= f.grouped_collection_select :book_id, @authors, :books, :name, :id, :title %>
<% end %>

についてのものだ。 本の手順通りに進めてこのテンプレートファイルを記述し(もちろん対応するルート定義と、コントローラーにメソッドが用意されている必要がある)、サーバーを立ち上げてアクセスすると、

undefined method 'books' for #<Author:~~~>

といったようなエラーに出くわし、ページへのアクセスができない。 これは、この時点でauthorモデルとbookモデルの間にアソシエーションを定義していないことに起因している。配布されているサンプルプログラムの方も同様の理由でエラーを吐く。

ここで問題になっているアソシエーションとはなんだろうか。少し調べた内容を置いておく。

アソシエーション

データベースのテーブル間の関係をrailsのモデル間の関係に落とし込んでrails側から操作できる用にするためのもの。データベースの基本的な用語についてはリンク参照。 テーブルの一つのレコードに対して別のテーブルの一つのレコードが結びついている場合を1:1の関係、別のテーブルのn個のレコードが結びついている場合を1:nの関係などと呼んだりする。

どうも、grouped_collection_selectメソッドはこのアソシエーションの情報を元に対応するbookモデルのパラメータを取得するはずだったのだが、記述がなかったので叶わなかった、ということみたいだ。

対処

そんなわけで、欠けているアソシエーションの情報を補ってやれば解決するということが分かった。今回実装したいauthorとbookの関係性は複数:複数の関係性で、このようなものは中間テーブルを介した表現が一般的らしい。 メソッドとしてはhas_and_belongs_to_manyを使い、下記の用になる。

.../railsbook/model/app/models/ 以下のbook.rbとauthor.rbにアソシエーションを記述する

class Book < ApplicationRecord
    has_and_belongs_to_many :authors #authorとのアソシエーションを追加
end
class Author < ApplicationRecord
    belongs_to :user
    has_and_belongs_to_many :books #booksとのアソシエーションを追加
end

has_and_belongs_to_manyメソッドは複数対複数のアソシエーションをモデルに追加するメソッド。対応するテーブルの関係に上下をつけない為、双方のモデルに記述する必要がある。

以上の変更を加えれば、/view/group_selectのページにアクセスできるようになる。

その他参考

https://teratail.com/questions/80528

テンプレートフレンド演算子の宣言方法

テンプレートクラスにフレンド演算子を定義しようとした時に嵌ったのでメモ。

新たに作ったテンプレートクラスに対する演算子を定義しようとする時、それらをフレンド演算子にすることはままあると思う。管理人もそのような場面に出くわし、素直に定義しようとしたところ思いの他嵌ってしまった。

結論から言うとテンプレートフレンド演算子を定義する時は、以下のようにする(他の方法があるかどうかは調べていない)。

#include <string>

template<typename T> class test_class; //下の定義でクラステンプレートを使用しているのでこちらも事前に定義しておく必要がある。
template<typename T> bool operator==(const test_class<T>, const test_class<T>); //メンバ演算子として演算子テンプレートを定義するので前方宣言が必要

template<typename T>
class test_class
{
  public:
    test_class(std::string string):str(string){}
    std::string get_string() const
    {
    return str;
    }

    friend bool operator==<T>(const test_class<T>, const test_class<T>);  //ここで定義されるのは演算子ではなく演算子テンプレートなのでコンパイラに知らせるために<T>が必要
 //上の一行は下記コードでも代用可能
    //friend bool operator==<>(const test_class<T>, const test_class<T>);
  private:
    std::string str;
};

template<typename T>
bool operator==(const test_class<T> one, const test_class<T> two)
{
    return one.str == two.str;
}

上記コードでコメントのある行はいずれものぞくとコンパイルエラーになる。コンパイラのバージョンは

$ clang++ -v  
Apple LLVM version 8.1.0 (clang-802.0.42) 

参考

stackoverflow.com

テンプレートクラスの演算子の多重定義 - C++ Builder / Turbo C++ 質問の木

仮引数が同じメンバ関数のオーバーロード

std::arrayの中身を読んでいたところ、以下のようなコードに出くわした。

  template<typename _Tp, std::size_t _Nm>
    struct array
    {
     ...
      iterator
      begin() noexcept
      { return iterator(data()); }

      const_iterator
      begin() const noexcept
      { return const_iterator(data()); }
}

仮引数と名前まで同じ関数がオーバーロードされている。 違うのは戻り値と、関数宣言の最後のconst指定だけだ。 関数のオーバーロードは仮引数が違わなければできないものではなかっただろうか。

調べてみると以下のようなページを見つけた。 ja.stackoverflow.com

どうやら、クラスのメンバ関数は暗黙の仮引数としてthisを持つらしい。 そして、constメンバ関数の場合、暗黙の仮引数thisにconstがつく。 だからオーバーロードができるのだ。

では、何のためにこのオーバーロードをしているのだろう。 試しに以下のコードをコンパイルしてみる。コンパイラはgcc4.2.1だ。

#include <array>
class testArray
{
  public:
    std::array<int, 3> ar;
    std::array<int, 3>::iterator begin()
    { return ar.begin(); }
};

int main(){
    testArray A = {1,2,3};
    auto Aitr = A.begin();
    return 0;
}

このコードは、当然のことだが問題なく動作する。 一方、testArrayをconst付きで宣言すると。

#include <array>

class testArray
{
  public:
    std::array<int, 3> ar;
    std::array<int, 3>::iterator begin()
    { return ar.begin(); }
 };

int main(){
    const testArray B = {1,2,3};
    auto Bitr = B.begin(); 
    return 0;
}

コンパイルすると

g++ -std=c++11 test.cpp
test.cpp:18:17: error: member function 'begin' not viable: 'this' argument has type
      'const testArray', but function is not marked const
    auto Bitr = B.begin();
                ^
test.cpp:8:34: note: 'begin' declared here
    std::array<int, 3>::iterator begin()
                                 ^
1 error generated.

のようになってしまう。 よく考えてみれば当然で、const付きのクラスのconst修飾されてないメンバ変数が返ってしまったら、後からその値を変えることができてしまう。 ちなみに、以下のようなコードであれば問題なく動作する。

#include <iostream>
#include <array>

class testArray
{
  public:
    std::array<int, 3> ar;
    std::array<int, 3>::const_iterator begin() const
    { return ar.cbegin(); }
};

int main(){
    const testArray B = {1,2,3};
    auto Bitr = B.begin();    
    return 0;
}

ところで関数宣言を

    const std::array<int, 3>::iterator begin() const
    {
    const std::array<int, 3>::iterator itr = ar.begin();
    return itr;
    }

のようにして実行すると

g++ -std=c++11 test.cpp
test.cpp:10:37: error: cannot initialize a variable of type 'const std::array<int,
      3>::iterator' (aka 'int *const') with an rvalue of type 'const_iterator'
      (aka 'const int *')
        const std::array<int, 3>::iterator itr = ar.begin();
                                           ^     ~~~~~~~~~~
1 error generated.

とエラーを吐かれてしまう。 これは、const std::array<int, 3>::iteratorがint型のconst pointerになるのに対し、ar.begin()の返り値はarray内でtypedefされているconst_iteratorのconst int型のpointerになってしまうからだ。こういう場合はstd::array<int, 3>::const_iteratorにするとうまくいく。

aggregate typeの初期化子リストによる初期化

C++11から統一初期化記法が登場した。それによって、配列やstd::arrayなどを初期化子リストで初期化することができるようになった。 具体的に言うと、下のようなことができるようになった。

#include<array>
...
int arr[3]{1, 2, 3};
std::array<int,  3> arr2{1,2,3};
...

自作コンストラクタで初期化子リストによる初期化をする場合、明示的コンストラクタを定義したければ、<initializer_list>ヘッダで定義されるstd::initializer_listクラスのオブジェクトを引数にとる、コンストラクタを定義する必要がある。

ただ、自作クラスがaggregate typeだった場合話が少し変わってくる。

aggregate typeとは、ユーザ定義のコンストラクタ、private又はprotectedな非静的データメンバ、基底クラス、仮想関数を一切含まないクラス、と配列のことだ。

この場合、初期化リストはメンバ変数を定義された順番に埋めていく。これも割と直感的な話だ。

気になるのはメンバ変数に配列が含まれる場合だ。この場合はどのように埋められていくのだろうか。気になったので試して見た。コードは以下でコンパイラはgcc4.2.1。

#include <iostream>
#include <array>

class Example
{
  public:
    int i;
    int iarr[3];
    char c;
};

class Example2
{
  public:
    int iarr[3];
    int i;
    char c;
};
    
class Example3
{
  public:
    int i;
    std::array<int,3> iarr;
    char c;
};

class Example4
{
  public:
    int i;
    std::array<std::array<int,2>,3> iarr;//arrayのarrayでも初期化はうまくいくのだろうか。
    char c;
};


int main(){
    Example A{1,2,3,4,'a'};
    Example2 B{1,2,3,4,'b'};
    Example3 C{1,2,3,4,'c'};
    Example4 D{1,21,22,31,32,41,42,'d'};
    
    std::cout << A.i << " ";
    for(auto elem : A.iarr)
    std::cout << elem << " ";
    std::cout << A.c << std::endl;

    std::cout << B.i << " ";
    for(auto elem : B.iarr)
    std::cout << elem << " ";
    std::cout << B.c << std::endl;

    std::cout << C.i << " ";
    for(auto elem : C.iarr)
    std::cout << elem << " ";
    std::cout << C.c << std::endl;

    std::cout << D.i << " ";
    for(auto elem : D.iarr){
    for(auto elem2 : elem)
        std::cout << elem2 << " ";
    }
    std::cout << D.c << std::endl;
    return 0;
}

実行結果は以下のようになった。

$ ./a.out
1 2 3 4 a
4 1 2 3 b
1 2 3 4 c
1 21 22 31 32 41 42 d

メンバ変数が配列だろうと気にせず前から埋めていっている。 arrayが入れ子になっている場合は、さらに内側のarrayの先頭から埋められていく。

ちなみに、以下のコードはコンパイルを通ってしまい、

#include <iostream>
#include <array>

class ExampleA
{
  public:
    int i;
    int iarr[3];
    char c;
};
    
class ExampleB
{
  public:
    int i;
    std::array<int,3> iarr;
    char c;
};

int main(){
    ExampleA A{1,2,3,'a',4};
    ExampleB B{1,2,3,'b',4};

    std::cout << A.i << " ";
    for(auto elem : A.iarr)
    std::cout << elem << " ";
    std::cout << A.c << std::endl;

    std::cout << B.i << " ";
    for(auto elem : B.iarr)
    std::cout << elem << " ";
    std::cout << B.c << std::endl;

    return 0;
}

出力結果は以下のようになる。

$ ./a.out
1 2 3 97  ^D
1 2 3 98  ^D

初期化子リストは便利だけれど、乱用すると意図せぬ初期化が容易に起こってしまうので、何も考えずに使っていいわけではなさそうだ。 当たり前ではあるけれども。

STL std::arrayの実装を読む(読めていない)

訳あって2次元コンテナの実装を目論んだのだがどのようにすればいいかよくわからない。 何か参考になるようなソースコードを探すことにしたのだが、そこで、STLのarrayは配列を単にラップしただけ、という話を思い出した。 これは参考にするにちょうど良いのではないだろうか? と言うことで、arrayの実装の解読に挑戦しようと思う。管理人の基礎知識は非常に薄っぺらいので途中で挫折するかもしないが、とにかく今のところは全部読み通す気でいる。

実装の場所

実装を読む、といったが、まず読むべき実装を見つけなければならない。参考までに書いておくと、管理人のOSはMacOS Sierra ver 10.12.6だ。以前かすかに/usr/include以下にSTLの実装を見かけた記憶があるので探してみたのだが、c++ディレクトリはあったものの、vectorはあれどarrayが見つからなかった。はて、と思い/usr/local/include/を探したところ、c++/5.3.0ディレクトリの中にarrayがある。どうやらこれを読めば良さそうだ。読むファイルすら見つけられずに挫折しなくてよかった。

ここで少し話がそれるが、/usrと/usr/localの差が気になったので書いておこう。僕はずっと、ディレクトリ構成などと言うものはOS開発者の気まぐれで決まるのだと思っていたが、どうやらそうではないらしい。/usrだの、/etcだののディレクトリ構成をちゃんと定めている「標準」が存在するのだ。例えばUNIXオペレーティングシステムにはFHS(Filesystem Hierarchy Standard)と言う規格が存在する。 このFHSによれば、/usrとは

/usr is the second major section of the filesystem. /usr is shareable, read-only data. That means that /usr should be shareable between various FHS-compliant hosts and must not be written to. Any information that is host-specific or varies with time is stored elsewhere.

/usr以下のファイルは基本的にread-onlyであるべきで、また、ホスト(ユーザーアカウントくらいのイメージでいいのだろうか)によらない情報が格納されているべき、と言うことらしい。OSアップデートなどで変わるのもこの領域(これに/usr/local以下は入っていないというのがまた紛らわしいのだが)だそうだ。まあOS共通のシステムが入っている、くらいの認識でいい気がする。

一方、/usr/localは

The /usr/local hierarchy is for use by the system administrator when installing software locally. It needs to be safe from being overwritten when the system software is updated. It may be used for programs and data that are shareable amongst a group of hosts, but not found in /usr.

こちらは、ローカルなソフトウェア(特定のユーザーしか使わないと言う意味?)を置いておく場所、みたいのもののようだ。OSアップデートなどでも変わることはない。「ホスト」だの「ローカル」だの、定義のよくわからない言葉が並んでいて正確に意味が取れない、という感じだがまあこれくらいでいいだろう。これ以上調べていると記事の趣旨が変わってしまう。

要するに、僕の使っているC++の実装はOS X標準のよりも新しいバージョンのもの、ということのようだ。

実装

さて、本題に入ろう。/usr/local/include/c++/5.3.0/arrayの内容を見ていこうと思う。ところで、てっきり.hppなどの拡張子がついていると思っていただけに拡張子がないのに戸惑ったが、冷静に考えてみれば#include のように読み込んでいるのだから当然か。

はじめのコメント部分を読み飛ばして、ヘッダを少し眺めてみる。

#ifndef _GLIBCXX_ARRAY
#define _GLIBCXX_ARRAY 1

#pragma GCC system_header

#if __cplusplus < 201103L
# include <bits/c++0x_warning.h>
#else

#include <stdexcept>
#include <bits/stl_algobase.h>
#include <bits/range_access.h>

パッと見てわかるのは冗長インクルードガードと例外処理のためのだろうか。あとはどうしても必要になり次第見ていくことにして、とりあえず先に進む。

namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_CONTAINER

  template<typename _Tp, std::size_t _Nm>
    struct __array_traits
    {
      typedef _Tp _Type[_Nm];

      static constexpr _Tp&
      _S_ref(const _Type& __t, std::size_t __n) noexcept
      { return const_cast<_Tp&>(__t[__n]); }

      static constexpr _Tp*
      _S_ptr(const _Type& __t) noexcept
      { return const_cast<_Tp*>(__t); }
    };

 template<typename _Tp>
   struct __array_traits<_Tp, 0>
   {
     struct _Type { };

     static constexpr _Tp&
     _S_ref(const _Type&, std::size_t) noexcept
     { return *static_cast<_Tp*>(nullptr); }

     static constexpr _Tp*
     _S_ptr(const _Type&) noexcept
     { return nullptr; }
   };

さて、こいつはなんなんだろう。namespaceと、その下の_GLIBCXX_BEGIN_NAMESPACE_CONTAINER(プリプロセッサマクロで定義された文字列だろう)はいいとしてその下だ。構造体_array_traitsを定義しているのはわかるのだが、しょっぱなからarrayの構造体かクラスが出てくると思っていたばかりに戸惑ってしまった。

まずはじめのtypedef _Tp _Type[_Nm]だが、これは確か配列のtypedefだ。typedefは基本的に

typedef 古い型名 新しい型名

と書くものだが、配列の時だけ

typedef 配列要素の古い型名 新しい型名[配列要素の個数]

という書き方になる。紛らわしいしusingを使って欲しいが、文句を言っても仕方がないか。

つまりここでは、テンプレレート第1引数の型の要素を第2引数個持った配列を_Typeと定義し直していることになる。arrayの中身のそのものという雰囲気の部分が出てきたぞ。

次に、その下の関数を見てみる。

 static constexpr _Tp&
      _S_ref(const _Type& __t, std::size_t __n) noexcept
      { return const_cast<_Tp&>(__t[__n]); }

これはなんだろう。どうやら配列の要素型の参照(_Tp&)を返すメンバ関数のよう。staticなので静的メンバ関数ということもわかる。静的メンバ関数とは、構造体やクラスの型の方と結びついている関数で、つまり、オブジェクトを作っていない段階でも呼び出すことができるものだ。対比して、オブジェクトに結びつけて使うメンバ関数は動的メンバ関数と呼ばれたりする。関数内部でconst_castが使われているので、どうも第1引数として渡した配列要素の参照、の第2引数番目のconstを外すための関数、らしい。constexprが付いているのは、この関数についてはコンパイル時に扱う型の情報が全てで揃うからだろう。

さて、noexceptだけよく分からない。これはなんだ?

c++日本語リファレンスによれば、C++11から追加された機能みたいだ。

throwキーワードによる例外仕様の代替。関数がどの例外を送出する可能性があるかを列挙するのではなく、例外を送出する可能性があるかないかのみを指定する。例外を送出する可能性がある関数にはnoexcept(false)を指定し、例外を送出する可能性がない関数にはnoexcept(true)もしくはnoexceptを指定する

この関数は例外を送出する可能性がない、ということを言っているらしい。なるほど、これでこの関数については大体わかったんじゃないだろうか。これが分かれば、その下の関数も自ずと何をしているのか分かる。同じことを配列そのもののポインタについてやっているだけだ。

では、二つ目の構造体はなんなのだろう。

 template<typename _Tp>
   struct __array_traits<_Tp, 0>
   { ... };

と宣言されていることから、どうやら上で定義した構造体のテンプレート引数が0個の場合について部分特殊化していることがわかる。 中身まで見ていこう

 template<typename _Tp>
   struct __array_traits<_Tp, 0>
   {
     struct _Type { };

     static constexpr _Tp&
     _S_ref(const _Type&, std::size_t) noexcept
     { return *static_cast<_Tp*>(nullptr); }

     static constexpr _Tp*
     _S_ptr(const _Type&) noexcept
     { return nullptr; }
   };

まず初の行の、struct _Type {}についてだ。はじめ、上で定義した型_Typeを下で使っているのかと思い、そんなことができるのか??と小一時間頭を悩ませたのだが、なんのことはない、特に何もしない構造_Typeを宣言しているだけだった。 関数の方は、内部でnullptrのcastをしている。空の配列のキャストをしようとしているので、エラーのような情報を返したいのだろうが、これはどういうことなのだろうか。c++日本語リファレンスを見るに、単に特定の型のポインタが空なことはnullptrを特定の型のポインタに入れることによって表すみたいだ。ただ不思議なのは、代入するだけでも暗黙に型変換されるはずのnullptrがわざわざ明示的にstatic_castされていることだ。しかもその後に関節参照演算子を作用させている。これってコンパイルエラーで落ちるんじゃないの?どういうこと?むしろこの部分特殊化された関数が呼ばれる場合は必ずコンパイルエラーで落ちるようになっているのだろうか。(追記:後日職場の先輩に聞いたのだが、これは未定義動作を確実に表現するためのもの出そうだ。大きさ0の配列へのアクセスは標準に未定義であることが記されているらしい。)

試しに

template<typename T>
T& func(T x)
{
    return *static_cast<T*>(nullptr);
}

int main(){
    func(10);
    return 0;
}

をコンパイルしてみると

test.cpp:8:12: warning: binding dereferenced null pointer to reference has
      undefined behavior [-Wnull-dereference]
    return *static_cast<T*>(nullptr);
           ^~~~~~~~~~~~~~~~~~~~~~~~~
test.cpp:12:5: note: in instantiation of function template specialization
      'func<int>' requested here
    func(10);
    ^

のようにコンパイルできてしまう。その上関数内で起こっているのはint型ポインタへキャストされたnullptrへのでリファレンスにも関わらずコンパイラはnullptrへのデリファレンスと主張している。(追記:コンパイル時にコンパイラが未定義だと教えてくれている。未定義動作であることを表したいのであれば、実装は目的にかなっているものだと言える。)

static constexpr _Tp*
     _S_ptr(const _Type&) noexcept
     { return nullptr; }

次の関数は割と分かりやすい。単に配列の要素型の空のポインタを返しているだけだ。

さて、ここを超えるとarrayの実装らしいところが見えてくる。やっとだ。とりあえず全体をざっと見てみよう。

    struct array
    {
      typedef _Tp                        value_type;
      typedef value_type*                pointer;
      typedef const value_type*                       const_pointer;
      typedef value_type&                            reference;
      typedef const value_type&                     const_reference;
      typedef value_type*                        iterator;
      typedef const value_type*                 const_iterator;
      typedef std::size_t                           size_type;
      typedef std::ptrdiff_t                            difference_type;
      typedef std::reverse_iterator<iterator>          reverse_iterator;
      typedef std::reverse_iterator<const_iterator>   const_reverse_iterator;

      // Support for zero-sized arrays mandatory.
      typedef _GLIBCXX_STD_C::__array_traits<_Tp, _Nm> _AT_Type;
      typename _AT_Type::_Type                         _M_elems;

      // No explicit construct/copy/destroy for aggregate type.

      // DR 776.
      void
      fill(const value_type& __u)
      { std::fill_n(begin(), size(), __u); }

      void
      swap(array& __other)
      noexcept(noexcept(swap(std::declval<_Tp&>(), std::declval<_Tp&>())))
      { std::swap_ranges(begin(), end(), __other.begin()); }

      // Iterators.
      iterator
      begin() noexcept
      { return iterator(data()); }

      const_iterator
      begin() const noexcept
      { return const_iterator(data()); }

      iterator
      end() noexcept
      { return iterator(data() + _Nm); }

      const_iterator
      end() const noexcept
      { return const_iterator(data() + _Nm); }

      reverse_iterator 
      rbegin() noexcept
      { return reverse_iterator(end()); }

      const_reverse_iterator 
      rbegin() const noexcept
      { return const_reverse_iterator(end()); }

      reverse_iterator 
      rend() noexcept
      { return reverse_iterator(begin()); }

      const_reverse_iterator 
      rend() const noexcept
      { return const_reverse_iterator(begin()); }

      const_iterator
      cbegin() const noexcept
      { return const_iterator(data()); }

      const_iterator
      cend() const noexcept
      { return const_iterator(data() + _Nm); }

      const_reverse_iterator 
      crbegin() const noexcept
      { return const_reverse_iterator(end()); }

      const_reverse_iterator 
      crend() const noexcept
      { return const_reverse_iterator(begin()); }

      // Capacity.
      constexpr size_type 
      size() const noexcept { return _Nm; }

      constexpr size_type 
      max_size() const noexcept { return _Nm; }

      constexpr bool 
      empty() const noexcept { return size() == 0; }

      // Element access.
      reference
      operator[](size_type __n) noexcept
      { return _AT_Type::_S_ref(_M_elems, __n); }

      constexpr const_reference
      operator[](size_type __n) const noexcept
      { return _AT_Type::_S_ref(_M_elems, __n); }

      reference
      at(size_type __n)
      { ... }

      constexpr const_reference
      at(size_type __n) const
      { ... }

      reference 
      front() noexcept
      { return *begin(); }

      constexpr const_reference 
      front() const noexcept
      { return _AT_Type::_S_ref(_M_elems, 0); }

      reference 
      back() noexcept
      { return _Nm ? *(end() - 1) : *end(); }

      constexpr const_reference 
      back() const noexcept
      { ... }

      pointer
      data() noexcept
      { return _AT_Type::_S_ptr(_M_elems); }

      const_pointer
      data() const noexcept
      { return _AT_Type::_S_ptr(_M_elems); }
    };

おお!それっぽいぞ。やっと求めていたものに巡り会えた。一部{ … }でで書かれている部分は実装を省略している。この部分もいずれは見ていけたらいいが、今はとりあえず置いておきたい。

さてまず気になるのは、この構造体、コンストラクタが明示的に定義されていないのだ。はて、arrayは統一初期化記法による初期化ができたと思うのだが、これはデフォルトコンストラクタがなくてもできるのだろうか。統一初期化記法による初期化とは下記のような初期化のことだ。

std::array<int, 4> ar{1, 2, 3, 4};

今回はメンバ変数が配列一つだからいいものの、複数あった場合、ユーザー定義のコンストラクタがないとどの配列を初期化すれば良いのか分からなくなってしまわないだろうか。それとも、今回のような、メンバ変数に配列が一個だけある場合だけ特別扱いされているのか?

今回のarrayのようなコンパイラ自動生成のコンストラクタしか持たない(正確にはメンバ変数も同様の性質を持っている必要があるが)型のことを「POD型」と言ったりする。この概念の説明は(http://nekko1119.hatenablog.com/entry/20120709/1341800447)の記事がとても分かりやすかった。しかし、POD型の統一初期化記法を調べて見てもいまいち欲しい情報が出てこない。しばらくあてどもなくインターネットを彷徨っていたのだが、MicrosoftのC++リファレンスページに「集約の初期化」という話を見つけた。

集約の初期化は、リストの初期化の一形態であり、次のような配列またはクラス型 (多くの場合は構造体や共用体) に使用されます。

  • プライベートまたはプロテクト メンバーでない。

  • 明示的に既定化または削除したコンストラクターを除いて、ユーザー定義のコンストラクターがない。

  • 基底クラスを持たない。

  • 仮想メンバー関数がない。

  • 非静的メンバーの中かっこまたは等号の初期化子がない。

まさにarrayクラスじゃないか。どうもこれは集約(aggregate)に相当するクラスらしい。それを知った上でコードをよく見てみれば、確かにコメントに// No explicit construct/copy/destroy for aggregate type.と書いてある。コメントはちゃんと読むべきだな。

リンク先の解説を読んで貰えればわかると思うが、aggregate typeに対する統一初期化は早くに宣言された変数から順に初期化されていくらしい。そして、配列がある場合はその配列も先頭から埋められていく(この話は別記事aggregate typeの初期化子リストによる初期化で試している)。今回の場合、後で見ていくが、メンバ変数は_AT_Type::_Type型の_M_elemsだけなので_M_elemsが先頭から順に埋められていくことになる。なるほど。コンストラクタがないのはむしろ統一初期化記法を適用できるようにするためなのか。

arrayを読み切るみたいな大言壮語を吐いてましたけど、今読めてるのはここまでです。ん〜先は長い。

また後日更新しようと思います。

EmacsからMastodonで遊ぶ

MastodonEmacsクライアント
github.com
があったので導入してみた。

まずレポジトリをクローンしてくる。
僕の場合、.emacs.dにelispディレクトリを作っているのでそこにクローンしてきた。

$ cd .emacs.d/elisp/
$ git clone https://github.com/jdenen/mastodon.el.git

Emacsの設定ファイルに以下を書き加える

(add-to-list 'load-path "/path/to/mastodon.el/lisp")  ;クローンしてきたmastodon.el内のlispディレクトリにパスを通す
(require 'mastodon)
(setq mastodon-instance-url "https://my.instance.url"); 自分のインスタンスのurlを読み込ませる

Emacsを再起動して

M-x mastodon

を実行する。
登録アカウントのEmailアドレスとパスワードを聞かれるので入力すると、Emacs上にmastodonのタイムラインが表示される。

トゥートは

M-x mastodon-toot

からできる
新しいウィンドウが立ち上がるので、そこに入力して"C-c C-c"で送信できる。
他には"f"でfav、"b"でブーストができたり。

Ubuntu16.04でウインドウをディスプレイ間移動させるショートカットの設定

立ち上げたウィンドウが思っていたディスプレイと違う場所に配置されてしまった時、動かしたいけどいちいちマウスに手を伸ばすのは癪、ということはあると思う。
そういう時は下記の方法でショートカットを設定するとストレスがなくなる。

ちなみに「Ubuntu ウィンドウ ディスプレイ 移動」みたいにグーグルで検索すると日本語で分かりやすく方法を解説してくれるブログが出てくるのだけど、実は途中から設定方法を間違えている。
僕はそこで割と混乱したので、同じように困る人の助けになれば、という気持ちで書いている。

もし英語が読めれば
askubuntu.com
を読めば解決します。

ウィンドウマネージャのCompizeをインストールしてくる。

$ sudo apt-get install compizconfig-settings-manager compiz-plugins

起動する。
f:id:purple-apple:20170430205155p:plain
Window Managementをクリックし、Putの左のチェックボックスをOnにする。
f:id:purple-apple:20170430205615p:plain
Putをクリックし、詳細設定の画面を開く。(もしかしたら自動でここに移動するかもしれない)
f:id:purple-apple:20170430205951p:plain
Put To Next Outputに適当なキーを設定する。
僕はCtrl+Shift+右矢印を設定したけど、この画像はCtrl+Alt+nにしている。

以上。