C++1z std::functionクラスのアロケータサポートを削除

C++11で導入されたstd::functionクラスには、アロケータを受け取るコンストラクタとassign()メンバ関数がありました。しかし、その仕様が不明確であったことから、正しい実装が行われてきませんでした。

  • GCCの標準ライブラリ実装であるlibstdc++では、アロケータを受け取るコンストラクタを提供していなかった
  • Clangの標準ライブラリ実装であるlibc++では、アロケータを受け取るコンストラクタはあったが、アロケータ引数は無視されていた
  • MSVCはアロケータを使用するが、代入の際にアロケータが伝搬されていなかった

そのため、これらの機能はC++1zで削除されます。

削除される機能

std::functionクラスの、アロケータを受け取るコンストラクタ、およびassign()メンバ関数

template<class A> function(allocator_arg_t, const A&) noexcept;
template<class A> function(allocator_arg_t, const A&, nullptr_t) noexcept;
template<class A> function(allocator_arg_t, const A&, const function&);
template<class A> function(allocator_arg_t, const A&, function&&);
template<class F, class A> function(allocator_arg_t, const A&, F);

template<class F, class A> void assign(F&&, const A&);

scoped allocatorから参照されるuses_allocatorの特殊化:

template<class R, class... ArgTypes, class Alloc>
struct uses_allocator<function<R(ArgTypes...)>, Alloc>
    : true_type { };

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z 古くなった機能を非推奨化

C++1zから、標準ライブラリのいくつかの機能が非推奨となります。非推奨となった機能は将来のバージョンで削除される可能性がありますのでご注意ください。

std::iteratorクラス

自作イテレータを作るときに基本クラスとして使用するstd::iteratorクラスですが、これを使ってもイテレータを定義するのはあまり簡単になりませんでした。

このクラスを使用することによって問題がより複雑になってしまうケースもありましたので、非推奨となります。

Boost.Iteratorのようなイテレータを簡単に定義するための新たな仕組みは提供されませんので、標準ライブラリの範囲では、イテレータは最初から最後まで自分で定義することになります。

std::allocatorクラスのいくつかのメンバと、std::allocator<void>

C++11からアロケータの中間インタフェースとデフォルト実装を提供するstd::allocator_traitsクラスが導入されました。それにともない、std::allocatorの多くの機能はいらなくなりました。

そのため、std::allocatorクラスの以下のメンバは、非推奨となります:

また、rebind操作のために必要だったstd::allocator<void>も、std::allocator_traitsの機能でまかなえるようになったので、この特殊化も非推奨となります。

std::is_literal_type型特性

constexprで使用できる型が、C++のバージョンアップにつれて増えていっています。そのうち何でも(もしくはほとんどの型を)扱えるようにする予定のため、この型特性は非推奨となります。

この型特性がほんとうに必要にならなくなるくらいに何でもconstexprで扱えるようになったときに、この機能は削除されます。

temporary buffer関係

std::get_temporary_buffer()関数とstd::return_temporary_buffer()関数は、関数内での一時的なメモリ確保のために、最適化されたメモリ確保の仕組みを提供することを期待して定義されましたが、実際には誰も特別視せず、誰も使いませんでした。

将来的にスタックからメモリ確保をするdynarrayといった仕組みが検討されていますが、temporary buffer関係の現在のAPIは設計として例外安全性やRAIIといったものが考慮されていないため、dynarrayが入ったとしてもtemporary buffer APIの内部を改善することはできないと判断され、非推奨となります。

std::raw_storage_iteratorクラス

未初期化メモリを指すイテレータから、オブジェクトを構築していく出力イテレータstd::raw_storage_iteratorクラスですが、オブジェクトを構築していくのであれば、アロケータとの連携ができることが重要となります。

std::raw_storage_iteratorはそのような用途には適さないということで、非推奨となります。

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z 隣接イテレータ

C++1zから、イテレータの分類に「隣接イテレータ (contiguous iterator)」というものが追加されます。

ランダムアクセスイテレータだけではポインタ操作ができるかわからないので、メモリが隣接していることを表すイテレータの分類を追加することになりました。隣接イテレータは、ランダムアクセスイテレータの要件を含みます。

また、コンテナのiteratorおよびconst_iteratorが隣接イテレータであるコンテナは「隣接コンテナ (contiguous container)」と呼ばれます。

以下のコンテナは、隣接コンテナであることが規定されます:

C++1zの段階ではコンセプトだけを決めて、隣接イテレータや隣接コンテナを判定する仕組みは提供されません。

訳語の選択

  • 英語の似たような単語としてcontinuousとcontiguousがある。
  • continuousは「アメリカの州がつながっている」とか「ヨーローッパの国が地続きになっている」という用途に使われる。これが「連続」と訳されることが多い。
  • contiguousは、音楽アルバムでトラックごとのつながりのような文脈で使われる。これはadjacencyとの類義語。
  • メモリの文脈ではcontiguousが使われる。
  • 日本語のメモリの文脈でも「隣接」という言葉が使われているようなので、隣接を採用した。

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z 非推奨だったauto_ptr, random_shuffle, 古い<functional>の機能を削除

C++1zでは、非推奨(deprecated)になっていた以下の機能が削除されます。既存のコードでこれらの関数を使用していた場合、コンパイルが通らなくなりますのでご注意ください。

古いスマートポインタ

  • std::auto_ptrクラス

C++11から非推奨。

代わりにstd::shared_ptrクラスやstd::unique_ptrクラスを使用してください。

古いバインダ

  • std::bind1st()関数
  • std::bind2nd()関数
  • std::binder1stクラス std::binder2ndクラス

C++11から非推奨。

代わりにstd::bind()関数もしくはラムダ式を使用してください。

関数ポインタから関数オブジェクトへの変換

  • std::ptr_fun()関数
  • std::pointer_to_unary_functionクラス std::pointer_to_binary_functionクラス

C++11から非推奨。

first_argument_typesecond_argument_typeといった型が必要なくなったため、これらの関数を使うことがなくなりました。

メンバ関数から関数オブジェクトへの変換

  • std::mem_fun()関数
  • std::mem_fun_ref()関数
  • std::mem_fun_tクラス
  • std::mem_fun1_tクラス
  • std::mem_fun_ref_tクラス
  • std::mem_fun1_ref_tクラス
  • std::const_mem_fun_tクラス
  • std::const_mem_fun1_tクラス
  • std::const_mem_fun_ref_tクラス
  • std::const_mem_fun1_ref_tクラス

C++11から非推奨。

代わりにstd::mem_fn()関数、std::bind()関数、ラムダ式といった機能を使用してください。

古いシャッフル関数

C++14から非推奨。

代わりにstd::shuffle()関数を使用してください。

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

変数の型名を取得する

Elixirで変数の型名を文字列として取得する。iexインタプリタのiコマンドみたいなことがしたかった。

defmodule Typename do
  @spec get(any) :: String.t()
  def get(x) do
    dict = %{
      &is_atom/1      => "atom type",
      &is_binary/1    => "binary type",
      &is_bitstring/1 => "bitstring type",
      &is_boolean/1   => "boolean type",
      &is_float/1     => "float type",
      &is_function/1  => "function type",
      &is_integer/1   => "integer type",
      &is_list/1      => "list type",
      &is_map/1       => "map type",
      &is_number/1    => "number type",
      &is_pid/1       => "pid type",
      &is_reference/1 => "reference type",
      &is_tuple/1     => "tuple type",
    }

    typename_list = Enum.filter_map(
      dict,
      fn {pred, _} -> pred.(x) end,
      fn {_, name} -> name end)
    if Enum.empty?(typename_list) do
      "unknown type"
    else
      Enum.join(typename_list, " && ")
    end
  end
end

x = "hello"
IO.puts "x type is #{Typename.get(x)}"

出力:

x type is binary type && bitstring type

C++1z INVOKEコンセプトに従った関数呼び出しをするinvoke()関数

C++11から入ったstd::mem_fn()関数、std::bind()関数、std::result_ofメタ関数などは、どれもINVOKEコンセプトという仕様のもとに定義されています。

これらの機能は、関数オブジェクトをただ呼び出すだけでなく、

  • メンバ関数ポインタ + レシーバー + 引数を指定してメンバ関数を呼び出したり、
  • メンバ変数ポインタ + レシーバーを指定してメンバ変数を取り出したり

といったことができます。そういった機能がINVOKEコンセプトというもので共通化されています。

C++1zでは、INVOKEコンセプトで関数呼び出しを行うstd::invoke()関数が導入されます。

#include <iostream>
#include <functional>

void f(int, char, double)
{
    std::cout << "f" << std::endl;
}

struct Functor {
    void operator()(int, char, double)
    {
        std::cout << "functor" << std::endl;
    }
};

struct X {
    int member_variable = 3;

    void member_function(int, char, double)
    {
        std::cout << "member_function" << std::endl;
    }
};

int main()
{
    // 関数
    std::invoke(f, 1, 'a', 3.14);

    // 関数オブジェクト
    std::invoke(Functor(), 1, 'a', 3.14);

    // メンバ変数
    X x;
    std::cout << std::invoke(&X::member_variable, x) << std::endl;

    // メンバ関数
    std::invoke(&X::member_function, x, 1, 'a', 3.14);
}

出力:

f
functor
3
member_function

宣言

// <functional>
namespace std {
    template <class F, class... Args>
    result_of_t<F&&(Args&&...)> invoke(F&& f, Args&&... args);
}

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

Phoenixで簡単なHTTPサーバー

仕事でElixirを使い始めました。Phoenixを調べています。

Phoenixのプロジェクトでデフォルトで作られるのはWebサイト的なアプリケーションですが、ビューはいらないので、デフォルトで作られたものをいじって、POSTでリクエストを受け取ってレスポンスを返すだけの簡単なサーバーにしました。

まず、コントローラを切り替えるweb/router.ex。デフォルトではブラウザからGETリクエストしか来ないようにCSRFトークンの検証とかをしていますが、これはいらないので削除します。

# web/router.ex
defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

  # 1. これいらない
  # pipeline :browser do
  #   plug :accepts, ["html"]
  #   plug :fetch_session
  #   plug :fetch_flash
  #   plug :protect_from_forgery
  #   plug :put_secure_browser_headers
  # end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", HelloPhoenix do
    # 2. :browserから:apiに変更
    pipe_through :api

    # 3. postも受け取れるようにする
    get "/", PageController, :index
    post "/hello", PageController, :hello
  end
end

次に、コントローラ側 web/controllers/page_controller.ex。HTMLをレンダリングする代わりに、レスポンスでJSONを返すようにします。

defmodule HelloPhoenix.PageController do
  use HelloPhoenix.Web, :controller

  def index(conn, _params) do
    conn |> json(%{key: "pong"})
  end

  def hello(conn, _params) do
    conn |> json(%{key: "pong"})
  end
end

あとはHTTPクライアントからアクセスしてみます。これは何を使ってもいいですが、Ruby標準のNet::HTTPを使いました。

require 'net/http'

# POST
uri = URI('http://localhost:4000/hello')
res = Net::HTTP.post_form(uri, 'key' => 'ping')
puts res.body # => {"key":"pong"}

# GET
uri = URI('http://localhost:4000')
puts Net::HTTP.get(uri) # => {"key":"pong"}

動きました。

C++1z 配列版unique_ptrの型変換

C++14まで、以下のコードは不適格(コンパイルエラー)でした:

unique_ptr<Foo const * const []> ptr1(new Foo*[10]);
Foo const * ptr = ptr1[9];

C++1zでは、このような型変換を許可するために、unique_ptrの配列に対する特殊化バージョンに、変換コンストラクタや変換代入演算子などが追加されます。

詳細な変更内容は提案文書を参照してください。

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z void_t

C++1zから、SFINAEによる「型に対して特性の操作ができるか」を判定するメタ関数の定義を容易にするために、void_tというパラメータで任意の数の型を受け取ってなにもせずvoidを返す型が定義されます。

// <type_traits>
namespace std {
  template <class...>
  using void_t = void;
}

私の著書『C++テンプレートテクニック 第2版』に掲載しているコードで、これまでのメタ関数定義と、void_tを使用したメタ関数定義を比べてみます。

型Tがiterator型を持っているか判定する

これまでの書き方でhas_iteratorメタ関数を定義する:

#include <type_traits>

struct has_iterator_impl {
    template <class T>
    static std::true_type check(typename T::iterator*);

    template <class T>
    static std::false_type check(...);
};

template <class T>
struct has_iterator :
    decltype(has_iterator_impl::check<T>(nullptr)) {};

#include <vector>
int main()
{
    static_assert(
        has_iterator<std::vector<int>>::value,
        "vectorはiterator型を持っている");

    static_assert(
        !has_iterator<int>::value,
        "intはiterator型を持っていない");
}

void_tを使用した場合:

#include <type_traits>

template <class, class = void>
struct has_iterator
    : std::false_type {};

template <class T>
struct has_iterator<T, std::void_t<typename T::iterator>>
    : std::true_type {};

#include <vector>
int main()
{
    static_assert(
        has_iterator<std::vector<int>>::value,
        "vectorはiterator型を持っている");

    static_assert(
        !has_iterator<int>::value,
        "intはiterator型を持っていない");
}

だいぶ短くなりました。

型Tが代入可能か判定する

これまでの書き方でis_assignableを定義する:

#include <type_traits>
#include <utility>

struct is_assignable_impl {
    template <class T>
    static auto check(T*) -> decltype(
        std::declval<T&>() = std::declval<const T&>(),
        std::true_type());

    template <class T>
    static auto check(...) -> std::false_type;
};

template <class T>
struct is_assignable
    : decltype(is_assignable_impl::check<T>(nullptr)) {};

struct A {
    A& operator=(const A&) = delete;
};

struct B {};

int main()
{
    static_assert(!is_assignable<A>::value, "Aは代入不可");
    static_assert( is_assignable<B>::value, "Bは代入可能");
}

void_tを使用した場合:

#include <type_traits>
#include <utility>

template <class, class = void>
struct is_assignable : std::false_type {};

template <class T>
struct is_assignable<
    T,
    std::void_t<decltype(std::declval<T&>() = std::declval<const T&>())>
> : std::true_type {};

struct A {
    A& operator=(const A&) = delete;
};

struct B {};

int main()
{
    static_assert(!is_assignable<A>::value, "Aは代入不可");
    static_assert( is_assignable<B>::value, "Bは代入可能");
}

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z コンテナのコピー・ムーブ、swap操作にnoexceptを追加

C++14までコンテナのnoexceptは最小限でしたが、C++1zではアロケータのコピーやムーブが例外を投げる可能性があるかどうかに基づいて、noexceptが追加されます。これで、標準ライブラリを使用したプログラムがより強い例外安全性を保証できるようになります。

まず、アロケータ側。ユーザーが定義するアロケータの要件として、is_always_equalという型をstd::true_typeの別名として定義すれば、同じ型のアロケータオブジェクトが2つある場合、それらが常に同値であるという表明になります。これは同時に、アロケータが状態を持たずコピーやムーブが例外を投げる可能性がないという表明にもなります。

struct MyAllocator {
    …
    using is_always_equal = std::true_type;
};

標準のstd::allocatorクラスのis_always_equalstd::true_typeとなります。

次にアロケータのデフォルトインタフェースと中間インタフェースを提供するstd::allocator_traitsクラスには、ユーザー定義のアロケータにis_always_equalが定義されない場合のデフォルトが定義されます。デフォルトではstd::is_empty型特性の結果型が設定されます。つまり、メンバ変数を持たないようなアロケータは自動的にis_always_equalが真になります。

template <class Alloactor>
struct allocator_traits {
    …
    using is_always_equal = /* Allocator::is_always_equalが定義されていればその別名となり、
                               そうでなければis_empty<Allocator> */;
};

コンテナ側に追加されるnoexceptは、以下のようになります。

  • basic_string, vector
    • デフォルトコンストラクタ (無条件でnoexcept)
    • アロケータを受け取るコンストラクタ (無条件でnoexcept)
    • ムーブコンストラクタ
    • ムーブ代入演算子
    • swap()
    • メンバ関数版のswap()
  • deque, forward_list, list, map, multimap, set, multiset, unordered_map, unordered_multimap, unordered_set, unordered_multiset

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。