擬似仮想関数テンプレート

たまに仮想関数をテンプレート関数にしたくなることがあるが、C++ではできない。

#include <iostream>
#include <typeinfo>

namespace lib {

class IDynamicType
{
  public:
    virtual ~IDynamicType() = default;

    template<typename StaticType>
    virtual void function(const StaticType &value) = 0; // NG: テンプレート関数は仮想にできない
};

} // namespace lib

namespace app {

class DynamicTypeA: public IDynamicType
{
  public:
    template<typename StaticType>
    void function(const StaticType &value) override {   // NG: テンプレート関数は仮想にできない
        std::cout << "DynamicTypeA::" << __func__ << "("
                  << typeid(value).name() << " " << value
                  << ")\n";
    }
};

class DynamicTypeB: public IDynamicType
{
  public:
    template<typename StaticType>
    void function(const StaticType &value) override {   // NG: テンプレート関数は仮想にできない
        std::cout << "DynamicTypeB::" << __func__ << "("
                  << typeid(value).name() << " " << value
                  << ")\n";
    }
};

} // namespace app

これは、非仮想のメンバーテンプレート関数と違って、新しい引数型を与えてfunction()が呼び出される度に、リンカーがIDynamicTypeクラスの仮想関数テーブルに対して新たなエントリーを追加しなければならなくなるからだ。

ただし、引数型を予め限定しても良いなら、クラステンプレートを使って擬似的に仮想関数テンプレートを作ることができる。

#include <algorithm>
#include <cstddef>
#include <memory>
#include <string>

namespace lib {

template<typename... TPack>
struct TypeList;

template<>
struct TypeList<>
{
    static constexpr std::size_t size = 0;

  protected:
    ~TypeList() = default;
};

template<typename THead, typename... TTail>
struct TypeList<THead, TTail...>: TypeList<TTail...>
{
    using head = THead;
    using tail = TypeList<TTail...>;

    static constexpr std::size_t size = sizeof...(TTail) + 1;

  protected:
    ~TypeList() = default;
};


template<class TStaticTypeList, std::size_t t_size = TStaticTypeList::size>
class DynamicType_IStaticType
    : public DynamicType_IStaticType<typename TStaticTypeList::tail>
{
  protected:
    virtual ~DynamicType_IStaticType() = default;

  public:
    using DynamicType_IStaticType<typename TStaticTypeList::tail>::function;
    virtual void function(const typename TStaticTypeList::head &value) = 0;
};

template<class TStaticTypeList>
class DynamicType_IStaticType<TStaticTypeList, 1>
{
  protected:
    virtual ~DynamicType_IStaticType() = default;

  public:
    virtual void function(const typename TStaticTypeList::head &value) = 0;
};


template<class TStaticTypeList>
class IDynamicType_IStaticTypeList
    : public DynamicType_IStaticType<TStaticTypeList>
{
};


using StaticTypeList = TypeList<int, std::string>;
using IDynamicType = IDynamicType_IStaticTypeList<StaticTypeList>;

class UseDynamicType
{
    std::unique_ptr<IDynamicType> m_idynamic;

  public:
    explicit UseDynamicType(std::unique_ptr<IDynamicType> &&idynamic)
        : m_idynamic{std::move(idynamic)}
    {}

  public:
    void use_function() {
        m_idynamic->function(3);
        m_idynamic->function("xyz");
    }
};

} // namespace lib


#include <iostream>
#include <typeinfo>

namespace app {

template<class TStaticTypeList, std::size_t t_size = TStaticTypeList::size>
class DynamicTypeA_StaticType
    : public DynamicTypeA_StaticType<typename TStaticTypeList::tail>
{
  protected:
    virtual ~DynamicTypeA_StaticType() = default;

  public:
    using DynamicTypeA_StaticType<typename TStaticTypeList::tail>::function;
    void function(const typename TStaticTypeList::head &value) override {
        std::cout << "DynamicTypeA_StaticType::" << __func__ << "("
                  << typeid(value).name() << " " << value
                  << ")\n";
    }
};

template<class TStaticTypeList>
class DynamicTypeA_StaticType<TStaticTypeList, 0>
    : public lib::IDynamicType
{
  protected:
    virtual ~DynamicTypeA_StaticType() = default;
};

class DynamicTypeA: public DynamicTypeA_StaticType<lib::StaticTypeList> {};


template<class TStaticTypeList, std::size_t t_size = TStaticTypeList::size>
class DynamicTypeB_StaticType
    : public DynamicTypeB_StaticType<typename TStaticTypeList::tail>
{
  protected:
    virtual ~DynamicTypeB_StaticType() = default;

  public:
    using DynamicTypeB_StaticType<typename TStaticTypeList::tail>::function;
    void function(const typename TStaticTypeList::head &value) override {
        std::cout << "DynamicTypeB_StaticType::" << __func__ << "("
                  << typeid(value).name() << " " << value
                  << ")\n";
    }
};

template<class TStaticTypeList>
class DynamicTypeB_StaticType<TStaticTypeList, 0>
    : public lib::IDynamicType
{
  protected:
    virtual ~DynamicTypeB_StaticType() = default;
};

class DynamicTypeB: public DynamicTypeB_StaticType<lib::StaticTypeList> {};

} // namespace app

随分と複雑になってしまったが、仮想関数テンプレートであるかのように呼び出すことができる。

int main()
{
    std::cout << "sizeof(lib::IDynamicType) = " << sizeof(lib::IDynamicType) << '\n';

    {
        std::unique_ptr<app::DynamicTypeA> a{new app::DynamicTypeA};
        std::cout << "sizeof(app::DynamicTypeA) = " << sizeof(app::DynamicTypeA) << '\n';
        a->function(1);
        a->function("abc");

        lib::UseDynamicType user{std::move(a)};
        user.use_function();
    }

    {
        std::unique_ptr<app::DynamicTypeB> b{new app::DynamicTypeB};
        std::cout << "sizeof(app::DynamicTypeB) = " << sizeof(app::DynamicTypeB) << '\n';
        b->function(2);
        b->function("def");

        lib::UseDynamicType user{std::move(b)};
        user.use_function();
    }
}

Clang 3.5.2/GCC 4.9.2でコンパイル~実行した結果は、以下のようになる。

sizeof(lib::IDynamicType) = 4
sizeof(app::DynamicTypeA) = 4
DynamicTypeA_StaticType::function(i 1)
DynamicTypeA_StaticType::function(Ss abc)
DynamicTypeA_StaticType::function(i 3)
DynamicTypeA_StaticType::function(Ss xyz)
sizeof(app::DynamicTypeB) = 4
DynamicTypeB_StaticType::function(i 2)
DynamicTypeB_StaticType::function(Ss def)
DynamicTypeB_StaticType::function(i 3)
DynamicTypeB_StaticType::function(Ss xyz)

Visual C++ 2015 RCでコンパイル~実行した結果は、以下のようになる。

sizeof(lib::IDynamicType) = 4
sizeof(app::DynamicTypeA) = 4
DynamicTypeA_StaticType::function(int 1)
DynamicTypeA_StaticType::function(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > abc)
DynamicTypeA_StaticType::function(int 3)
DynamicTypeA_StaticType::function(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > xyz)
sizeof(app::DynamicTypeB) = 4
DynamicTypeB_StaticType::function(int 2)
DynamicTypeB_StaticType::function(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > def)
DynamicTypeB_StaticType::function(int 3)
DynamicTypeB_StaticType::function(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > xyz)

クラス構成は、正確に表現できていないが以下の図のようになっている。

仮想関数テンプレート

引数型が限定される上に、インターフェイス側も実装側も複雑なので、ここまでして実現したいことがあるかはわからないが、C++11の勉強がてら作ってみたので記録として残しておく。

参考

C++11:定数式の一般化とその保障:constexpr

C++11では、定数式を定義できるようになった。

enum class Flag: unsigned int { good = 1, fail = 2, bad = 4, eof = 8 };

constexpr Flag operator|(Flag x, Flag y)
{
    return static_cast<Flag>(
        static_cast<unsigned int>(x) | static_cast<unsigned int>(y));
}

void func(Flag f)
{
    switch ( f ) {
    case Flag::bad:           /* ... */ break;
    case Flag::eof:           /* ... */ break;
    case Flag::bad|Flag::eof: /* ... */ break;
    default:                  /* ... */ break;
    }
}

定数式には、コンパイル時に不明な値は利用できない。

int x1 = 7;
constexpr int x2 = 7;
constexpr int x3 = x1; // エラー:初期化子が定数式でない
constexpr int x4 = x2; // OK

constexpr関数は、単純でなければならない。具体的には、return文は一つだけ許され、局所変数や選択文(if文など)や繰り返し文(for文など)は認められない。また、副作用を持ってもいけない。
ただし、条件式と再帰は使えるので、本当に必要であればあらゆるものをconstexpr関数にできる。(これらの制限の一部は、C++14で緩和された。)

constexpr int fact(int n)
{
    return (n > 1) ? n * fact(n - 1) : 1;
}

関数定義におけるconstexprは、コンパイル時に評価できることを示し、オブジェクト定義におけるconstexprは、初期化子をコンパイル時に評価する必要があることを示す。
なお、constexpr関数は、非定数の実引数を与えて呼び出すこともできるので、非定数用に同じ内容の関数を定義する必要はない。

void func(int n)
{
    int f5 = fact(5);            // コンパイル時に評価される可能性がある
    int fn = fact(n);            // 実行時に評価される

    constexpr int f6 = fact(6);  // コンパイル時に評価される
    constexpr int fnn = fact(n); // エラー:コンパイル時に評価できない
}

constexprは、クラスのコンストラクターに付けることもできるが、コンストラクターの本体は空でなければならず、メンバー初期化子並びかオブジェクトの初期化子で全てのメンバー変数を初期化しなければならない。

struct Point
{
    int x, y;
    constexpr Point(int xx = 0, int yy = 0): x{xx}, y{yy} {}
};

constexpr Point origin;
constexpr int ox{origin.x};
constexpr Point points[]{Point{1,2}, Point{3,4}, Point{5,6}};
constexpr int x{points[1].x};

constexprコンストラクターを持つクラスは、リテラル型と呼ばれる。

constexprは、全てのconstを置き換えるものではないことに注意しよう。

  • constexprは、「式をコンパイル時に評価する」ということを示し、主として定数の指定に用いる。
  • constは、「値を変更しない」ということを示し、主としてインターフェイスの指定に用いる。

C++11:スコープ付きの強く型付けされた列挙体:enum class

C++11では、enum(従来の列挙体)の3つの問題を解決したenum classが導入された。

  • 暗黙的にint型へ変換できるため、整数として振舞わせたくない場合にエラーの原因となる。
  • 列挙子がスコープの外から見えるため、名前の衝突を引き起こす。
  • 根底型(内部表現として利用される型)を指定できないため、混乱や互換性の問題の元となる。また、前方宣言できない。

enum classは、強く型付けされる。

enum Alert { red, blue };       // 従来の列挙体
enum class Color { red, blue }; // スコープ付きの強く型付けされた列挙体

Alert a1 = 7;           // エラー:int型からの変換不可
Color c1 = 7;           // エラー:int型からの変換不可

int a2 = red;           // OK
int c2 = Color::red;    // エラー:int型への変換不可

Alert a3 = blue;        // OK
Color c3 = Color::blue; // OK

enum classの列挙子は、列挙体のスコープに入る。

enum Alert { red, blue };
enum class Color { red, blue };

enum Foo { red, blue };         // エラー:名前の衝突
enum class Bar { red, blue };   // OK

Alert a1 = blue;        // OK
Color c1 = blue;        // エラー

Alert a2 = Alert::blue; // OK(C++98では不可)
Color c2 = Color::blue; // OK

根底型を指定できるようになったことで、列挙体の相互運用性と大きさの保障が単純になった。

enum Alert { red, blue };             // 実装定義(C言語ではint)
enum class Color: char { red, blue }; // 1バイト

根底型には汎整数型(論理型、文字型、整数型)を指定でき、デフォルトではint型となる。
前方宣言も可能になった。

// 前方宣言
enum Alert;             // エラー
enum class Color: char; // OK

// 前方宣言の利用
void foo(Alert a); // エラー
void bar(Color c); // OK

// 定義
enum Alert { red, blue };
enum class Color: char { red, blue };

なお、C++11では、単なるenumでも根底型を指定でき、その場合は前方宣言もできる。

// 前方宣言
enum Alert: char; // OK

// 前方宣言の利用
void foo(Alert a);

// 定義
enum Alert: char { red, blue };

マイコーディングルール

  • 単なるenumではなく、enum classを使う。

参考

C++11:デフォルト関数の生成の制御:defaultおよびdelete

C++11では、「コピーを禁止する」という慣用句(イディオム)を直接表現できるようになった。

class Foo
{
    Foo(const Foo &) = delete;
    Foo &operator=(const Foo &) = delete;
    // ...
};

C++98では、private宣言して定義しないという手法がよく用いられた。

class Foo
{
  private:
    Foo(const Foo &);
    Foo &operator=(const Foo &);
    // ...
};

逆に、デフォルトの動作が望ましいなら、それを明示することもできる。

class Bar
{
    // ...
  public:
    Bar() = default;
    ~Bar() = default;
    Bar(const Bar &) = delete;
    Bar &operator=(const Bar &) = delete;
    Bar(Bar &&) = default;
    Bar &operator=(Bar &&) = default;
    // ...
};

=defaultは、デフォルトで生成されるどんな関数にでも使用でき、=deleteは、どんな関数にでも使用できる。

class D
{
    double d;
  public:
    D(double);
    D(int) = delete;
};

int main()
{
    D d1(1.2); // OK
    D d2(3);   // エラー
}

デフォルトでは、クラスに対して以下の関数が生成される。

プログラマーがこれらの関数を宣言すると、コンパイラーは次の規則に基づいて生成しない。

  1. プログラマーコンストラクターを一つでも宣言すれば、コンパイラーはデフォルトコンストラクターを生成しない。
  2. プログラマーがコピー関数、ムーブ関数、デストラクターの何れか一つでも宣言すれば、コンパイラーはコピー関数、ムーブ関数、デストラクターの何れも生成しない。

ただし、2つ目の規則は後方互換性維持のために不完全な効力しか持たず、私の環境(clang 3.4.2)ではBjarne Stroustrup氏の記事書籍とも異なるよくわからないコンパイル結果となった。
そのため、コピー関数、ムーブ関数、デストラクターの何れか一つでも宣言する場合は、その他の関数も明示的に宣言した方が良いだろう。

C++11:入れ子になったテンプレート引数

C++11では、入れ子になったテンプレート引数の閉じ括弧が連続する場合に空白が不要になった。

std::map<int, std::vector<std::string>> m;

C++98では、右シフト演算子や入力演算子と区別するために、空白が必要だった。

std::map<int, std::vector<std::string> > m;

参考

C++11:範囲for文

C++11では、ある範囲内の全要素の処理を簡潔に書けるようになった。

for ( auto x : 範囲 ) {
    // ...
}

「範囲」は、std::vectorのように反復子を返すメンバー関数begin()/end()が定義されているクラスのオブジェクトでも良いし、組み込み配列のように非メンバー関数begin()/end()を適用できるオブジェクトでも良い。(メンバー関数の方が優先される。)

void func(std::vector<double> &v)
{
    for ( auto x : v ) std::cout << x << "\n"; // 要素はxにコピーされる
    for ( auto &x : v ) ++x;                   // 要素を参照すると更新できる
    for ( const auto x : {1,2,5,13,34} ) std::cout << x << "\n";
}
int v[] = {1,2,5,13,34};
for ( auto x : v ) std::cout << x << "\n";

範囲for文を支援するために、標準ライブラリの<iterator>で範囲アクセス関数std::begin()/std::end()が定義されている。

namespace std {
    // クラス用
    template<typename C>
    auto begin(C &c) -> decltype(c.begin());
    template<typename C>
    auto begin(const C &c) -> decltype(c.begin());
    template<typename C>
    auto end(C &c) -> decltype(c.end());
    template<typename C>
    auto end(const C &c) -> decltype(c.end());
    // 組み込み配列用
    template<typename T, size_t N>
    auto begin(T (&array)[N]) -> T*;
    template<typename T, size_t N>
    auto end(T (&array)[N]) -> T*;
}

つまり、冒頭の範囲for文は以下のコードと等価である。

for ( auto __begin = begin(範囲), __end = end(範囲);
      __begin != __end; ++__begin )
{
    auto x = *__begin;
    // ...
}

範囲アクセス関数を直接使うことはあまりないだろうが、呼び出し方には注意が必要だ。

#include <algorithm>
#include <iterator>

template<typename Range, typename Function>
void for_each_all(Range &range, Function func)
{
    // 組み込み配列へ対応するために、using宣言する必要がある。
    using std::begin;
    using std::end;
    // 非標準の範囲アクセス非メンバー関数へ対応するために、名前空間修飾無しで呼び出す必要がある。
    auto first = begin(range);
    auto last = end(range);
    std::for_each(first, last, func);
}

#include <cstddef>

struct MyContainer
{
    static constexpr std::size_t m_a_size = 3u;
    int m_a[m_a_size];
    MyContainer() : m_a{4, 5, 6} {}
};

int *begin(MyContainer &c) { return c.m_a; }
int *end(MyContainer &c) { return c.m_a + MyContainer::m_a_size; }

#include <iostream>
#include <vector>

void print_line(int i)
{
    std::cout << i << "\n";
}

int main()
{
    std::vector<int> v = {1, 2, 3};
    MyContainer c;
    int a[] = {7, 8, 9};

    for_each_all(v, print_line);
    for_each_all(c, print_line);
    for_each_all(a, print_line);
}

高橋氏の記事の「std名前空間のbegin()/end()をusing宣言しているのは、配列版を考慮するため。」という説明は、よくわからなかった。)

参考

C++11:初期化子からの型の導出:auto

C++11では、「式」から値の型を導出することができるようになった。

auto x = 式;

初期化子から変数の型を導出できるauto型指定子は、型を書くことが面倒な場合や、正確に知ることが難しい場合に便利だろう。

template<class T>
void print_all(const std::vector<T> &v)
{
    for ( auto i = v.begin(); i != v.end(); ++i ) {
        std::cout << *i << "\n";
    }
}

C++98では、以下のように書く必要があった。

template<class T>
void print_all(const std::vector<T> &v)
{
    for ( typename std::vector<T>::const_iterator i = v.begin(); i != v.end(); ++i ) {
        std::cout << *i << "\n";
    }
}

型がテンプレート引数に強く依存している場合は、auto無しに書くことは難しいだろう。

template<class Tx, class Ty>
auto multiply(const Tx &x, const Ty &y) -> decltype(x * y)
{
    return x * y;
}

戻り値型の後方宣言decltypeについては、後日説明予定。)

導出される型に対して、constや&(参照)などの型指定子や修飾子を付けることもできる。

void func(std::vector<std::string> &v)
{
    for ( const auto &s : v ) { // sはconst std::string &
        // ...
    }
}

なお、autoの古い意味(「これはローカル変数である」)は、今では不正になった。

マイコーディングルール

  • わざわざ型を明示すべき特別な事情が無い限り、autoを用いる。
  • autoで変数を宣言する場合は、=構文を用いる。{}構文を用いると、std::initializer_list<T>型を導いてしまうため。(C++17で変更される)
auto x = 1; // xはint
auto y{1};  // yはintではなくstd::initializer_list<int>(C++17ではint)