C++のコンパイルエラー爆発を競うコンテスト

Results of the Grand C++ Error Explosion Competition

少し前に、C++コンパイルエラーの長さを競うコンテストが開催されていました。

受賞のカテゴリは2つあり、ひとつめは最小コードで最大のコンパイルエラーを出した人、もうひとつは芸術的な評価による受賞です。

最小コードで最大のコンパイルエラー

この部門で優勝したのはEd Hanwayさんという方で、ソースコード量に対して59億倍のコンパイルエラーメッセージを出力したそうです。

それには、自身を2回インクルードするという手法が使われていたそうです。

#include ".//.//.//.//jeh.cpp"
#include "jeh.cpp"

次点として、インクルードに後方参照を使用した、7億9千万倍のコンパイルエラーを出力するコード:

#include "set>.cpp"
#include "set>.cpp"

可変引数テンプレートの再帰を利用した、6億5700万倍のコンパイルエラーを出力するコード:

template<class T,class...>class C{C<T*const,T,C>a;C<T,C>b;};C<int>c;

などがありました。

最高のチート

このコンテストの中では、多くのおもしろい試みが行われて、たとえばC++のダイグラフ拡張を使ったものがありました。検証スクリプトのゼロ除算バグを悪用しようとしたもの、評価スクリプトにパッチを当てて無限のエラーを出力させようとしたものなどもありました。

しかし最高のチートを行った人はこれとは別の方向に行きました。そのコード自体は一つのスペースが入ってるだけのものでしたが、ヘッダ探索のパスが以下のように書かれていたそうです。

/usr/include; perl -e "@c=\"x\"x(2**16); while(1) {print @c}" 1>&2

C++コンパイラがこれを実行すると、シェルコードがサンドボックスを脱出するのだそうです。

最高の驚き

以下のコードは、素朴ではありますが、予期せずコンパイルエラーが爆発します。

template<class T>class L{L<T*>operator->()};L<int>i=i->

Clangは「セミコロンが不足している」と正しく認識してくれますが、その後に無限のテンプレート再帰によってコンパイラがセグメンテーションフォルトになります。 この種の問題を防ぐために、IDEのコード補完に対するニーズを垣間見ることができる・・・そうです。

まだもうちょっとありますが

紹介はこのあたりにしておきます。また来年もやるかもよ、とのことですので、興味がある方は上記ページをウォッチしておくといいと思います。

C++14 complexのconstexpr対応

N3302 Constexpr Library Additions: complex, v2

C++14では、複素数を表すstd::complexクラスのconstexpr対応が行われます。

constexpr化するのは、以下の関数です。

constexpr化しないのは、以下の関数です。

参照

C++14 shared_mutexがshared_timed_mutexに改名

C++14に予定されていたmultiple-readers / single-writerパターンのshared_mutexクラスですが、shared_timed_mutexに名称が変更になりました。このクラスに元々、タイムアウト付きのロック取得関数が含まれていたからです。

// <shared_mutex>ヘッダ

namespace std {
  class shared_timed_mutex;

  template <class Mutex>
  class shared_lock;
}

タイムアウト付きロック取得のないshared_mutexは、以下のペーパーで改めて提案されています。

N3961 A proposal to add shared_mutex (untimed)

これは、C++14には今のところ予定されていません。

参照

派生クラスの仮想関数には、virtualを付ける必要はない

基本クラスで仮想関数を定義したら、派生クラスではその関数に、virtualを付ける必要はありません。付けなくても、自動的に仮想関数になります。

#include <iostream>

struct Base {
    virtual void f()
    {
        std::cout << "Base" << std::endl;
    }
};

struct Derived : Base {
    void f() override
    {
        std::cout << "Derived" << std::endl;
    }
};

int main()
{
    Derived d;
    Base& b = d;

    b.f();
}

出力:

Derived

C++03においては、派生クラスでも「この関数はオーバーライドを意図してるんだ」というのを示すためにvirtualを付けることに意義がありました。

C++11ではoverrideキーワードを使えばその意図は示せるので、派生クラスの仮想関数にvirtualを付ける必要はありません。

Boost.Compute v0.1

Boost.Compute v0.1 Released - boost.devel

GPGPUと並列プログラミングのためのライブラリ、Boost.Computeのv0.1がリリースされました。Boostに正式に入っているライブラリではありません。

#include <vector>

#include <boost/compute/algorithm/copy.hpp>
#include <boost/compute/container/vector.hpp>

namespace compute = boost::compute;

int main()
{
    // ホスト環境に配列を作る
    int host_data[] = { 1, 3, 5, 7, 9 };

    // デバイス上にvectorを作る
    compute::vector<int> device_vector(5);

    // ホストからデバイスにデータをコピーする
    compute::copy(host_data,
                  host_data + 5,
                  device_vector.begin());

    // ホスト上にvectorを作る
    std::vector<int> host_vector(5);

    // デバイスからホストにデータを戻す
    compute::copy(device_vector.begin(),
                  device_vector.end(),
                  host_vector.begin());

    return 0;
}

このライブラリは、OpenCLをラップして作られています。

乱数分布と8ビット整数

C++標準ライブラリの乱数分布クラスは、テンプレートパラメータとして、IntTypeUIntTypeRealTypeのいずれかをとります。

整数型IntTypeUIntTypeとしては、std::is_integral特性を満たす整数型ではなく、以下の整数型のみが許可されています。

  • short
  • int
  • long
  • long long
  • unsigned short
  • unsigned int
  • unsigned long
  • unsigned long long

この中には、charがありません。

そのため、8ビットの固定精度整数型であるstd::int8_tstd::uint8_tは、分布クラスが生成する整数型として使えません。

だいたいの実装では、テンプレートパラメータの型の制限はstd::is_integralで行われているので、たとえばGCC 4.8(libstdc++)やClang 3.4(libc++)だと、以下のコードは通ります。

#include <iostream>
#include <random>
#include <cstdint>

int main()
{
    std::random_device seed_gen;
    std::mt19937 engine(seed_gen());
   
    std::uniform_int_distribution<std::uint8_t> dist {
        0,
        std::numeric_limits<std::uint8_t>::max()
    };
   
    for (int i = 0; i < 10; ++i) {
        int result = dist(engine);
        std::cout << result << std::endl;
    }
}

出力例:

231
206
155
107
47
226
242
143
119
34

なので、とくに意識せず8ビット整数を分布クラスで作っていた方は、「C++11の規格上では、許可されていない」というのを理解した上で使ってください。

ちなみに、Boost.Randomにはとくに制限がありません。

参照

charは許可していいだろう、という問題報告はすでにされていますが、C++14に入る予定は今のところないようです。

LWG 2326. uniform_int_distribution<unsigned char> should be permitted

C++ポケットリファレンスの増刷が決まりました

『C++ポケットリファレンス』という本を書きました!

C++ポケットリファレンスについて書ききれなかった、いくつかのこと

先ほど技術評論社の編集さんから連絡があり、『C++ポケットリファレンス』の増刷が決まったそうです。

C++ポケットリファレンス - Amazon

C++ポケットリファレンス - 技術評論社

ご購入いただいたみなさん、ありがとうございます。みなさんの向上心、好奇心に支えられて、本書がより長く書店に並ぶことができます。

apply()を標準アルゴリズムと組み合わせられるように改善する

N3915 apply() call a function with arguments from a tuple (V3)

現在、C++14後のLibrary Fundamentals TSに提案されているapply()関数。これは、タプルを引数として関数を呼び出すというユーティリティ関数ですが、機能が小さすぎるので実用に耐えません。

この関数は、実際には複数のRangeをzipしてアルゴリズムに渡すために使用します。そのため、標準アルゴリズムと組み合わせて使えなくてはなりません。標準アルゴリズムと組み合わせられるようにするには、「複数引数を受け取る関数を、タプルを引数として実行する形に変換する」という機能とその引数適用を、2段階に分けて行える必要があります。

vector<tuple<T1, T2, T2>> v;
for_each(v.begin(), v.end(), make_apply([](T1 a, T2 b, T3 c) {
    cout << a << ' ' << b << ' ' << c << endl;
}));

これと同等の機能は、Boost.Fusionにだいぶ前からmake_fused()という関数で提供されています。標準にこの機能を導入するのであれば、Boost.Fusionの経験を反映させたものにするべきだと思います。

2段階化の実装は、apply()関数のラッパーとして実装できます。

#include <tuple>
#include <utility>

template<typename F, typename Tuple, size_t... I>
auto apply_impl(F&& f, Tuple&& args, std::index_sequence<I...>)
{
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
}

template<typename F, typename Tuple,
         typename Indices = std::make_index_sequence<std::tuple_size<Tuple>::value>>
auto apply(F&& f, Tuple&& args)
{
    return apply_impl(std::forward<F>(f), std::forward<Tuple>(args), Indices());
}

template<typename F, typename Tuple, size_t... I>
auto apply_impl(F&& f, const Tuple& args, std::index_sequence<I...>)
{
    return std::forward<F>(f)(std::get<I>(args)...);
}

template<typename F, typename Tuple,
         typename Indices = std::make_index_sequence<std::tuple_size<Tuple>::value>>
auto apply(F&& f, const Tuple& args)
{
    return apply_impl(std::forward<F>(f), args, Indices());
}

template <class F>
class apply_functor {
    F f_;
public:
    explicit apply_functor(F&& f)
        : f_(std::forward<F>(f)) {}

    template <class Tuple>
    auto operator()(Tuple&& args)
    {
        return apply(std::forward<F>(f_), std::forward<Tuple>(args));
    }

    template <class Tuple>
    auto operator()(const Tuple& args)
    {
        return apply(std::forward<F>(f_), args);
    }
};

template <class F>
apply_functor<F> make_apply(F&& f)
{
    return apply_functor<F>(std::forward<F>(f));
}

サンプル:

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

int main()
{
    std::vector<std::tuple<int, char, std::string>> v = {
        {1, 'a', "Alice"},
        {2, 'b', "Bob"},
        {3, 'c', "Carol"}
    };
    
    std::for_each(v.begin(), v.end(),
      make_apply([](int a, char b, const std::string& c) {
          std::cout << a << ' ' << b << ' ' << c << std::endl;
      }
    ));
}

出力:

1 a Alice
2 b Bob
3 c Carol

という意見を出そうかと思っています。ようやく実装コードを書く時間がとれたので、もうちょっと改善できるところがないかを検討してから出します。

参照

アロケータ状態の伝搬を制御する

C++11では、ユーザー定義のアロケータが状態を持てるようになりました。それにともなって、コンテナのコピー、ムーブ、swap時に、アロケータの状態を伝搬するかどうかを制御できるようになりました。

コピー代入、ムーブ代入、swapの伝搬を制御

アロケータの状態を伝搬するには、ユーザー定義アロケータに、以下のメンバ型を定義します。

  • propagate_on_container_copy_assignment : コンテナのコピー代入時に状態を伝搬する。
  • propagate_on_container_move_assignment : コンテナのムーブ代入時に状態を伝搬する。
  • propagate_on_container_swap : コンテナのswap時に状態を伝搬する。

これらのメンバ型を、<type_traits>ヘッダで定義されるstd::true_typeの別名として定義すれば、コンテナの各ポイントで、アロケータの状態が伝搬されるようになります。

これらのメンバ型を定義しない場合は、アロケータの状態は伝搬されません。

C++11のstd::allocatorはこれらのメンバ型を定義していません。

C++14では、謎の日本人Ai Azumaさんの提案によって、std::allocatorに以下のメンバ型が定義されるようになります。

typedef std::true_type propagate_on_container_move_assignment;

つまり、コンテナのムーブ代入時に、std::allocatorオブジェクトの状態が自動的にムーブ先に伝搬されるようになります。

コピー構築に使用するアロケータオブジェクトを選択

また、状態を伝搬するメンバ型に加えて、コピー構築に使用するコンテナを選択する関数もあります。ユーザー定義のアロケータに、以下のpublicメンバ関数を定義します。

MyAllocatorType select_on_container_copy_construction() const;

これを定義すれば、コンテナのコピー構築時に、自身のアロケータをそのまま使うか、あるいは新たなアロケータオブジェクトを作るのかを選択できます。この関数を定義しない場合は、コピー元コンテナが持つアロケータのコピーを使用します。

参照

内部的な型のアロケート

C++11で導入されたライブラリのいくつかは、内部で使用する型のオブジェクトをアロケートするアロケータを、公開インタフェースとして要求します。

たとえば、std::function<R(Args...)>

template <class F, class Alloc>
void function::assign(F&& f, const Alloc& alloc);

std::functionは、関数オブジェクト型Fを内部的にアロケートします。

たとえば、std::shared_ptr<T>

template <class Y, class Deleter, class Alloc>
shared_ptr(Y* p, Deleter d, Alloc a);

std::shared_ptrは、内部で参照カウンタをアロケートします。参照カウンタの型はprivateなので、ユーザーは型名がわかりません。

こういうインタフェースに対しては、てきとうな要素型のアロケータオブジェクトを渡します。std::allocator<int>でもstd::allocator<MyClassType>でもかまいません。 そして、これらの関数の内部で、適切な要素型にアロケータをrebindされ、それらの要素型のオブジェクトがアロケートされます。

std::functionの例:

#include <iostream>
#include <functional>

int ident(int x) { return x; }

int main()
{
  std::function<int(int)> f;

  // 関数とアロケータを代入。
  //
  // ※ここではint型を対象とするアロケータを渡しているが、
  // 内部で適切な関数の型にrebindして使われる。
  f.assign(ident, std::allocator<int>());

  int result = f(1);
  std::cout << result << std::endl;
}

std::shared_ptrの例:

#include <iostream>
#include <memory>

int main()
{
    std::shared_ptr<int> p = {
        new int(3),
        std::default_delete<int>(),
        std::allocator<void>()
    };

    std::cout << *p << std::endl;
}

これらの関数の仕様上、rebindすることは明確に記載されてはいないのですが、Allocator requirementsを満たすアロケータ型を要求してるという点と、rebindを使うしか方法がない、という可能性の検討の結果、rebindを使うということがわかります。

std::functionstd::shared_ptrのほか、std::promiseもアロケータをrebindします。std::promiseは内部的にstd::shared_ptrを作りますが、そのことが公開インタフェースにはなっていないからです。