変数の型名を取得する
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); }
参照
- N3727 A proposal to add
invoke
function template - N4169 A proposal to add
invoke
function template (Revision 1)
お断り
この記事の内容は、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は代入可能"); }
参照
- N3911 TransformationTrait Alias
void_t
- N4502 Proposing Standard Library Support for the C++ Detection Idiom, v2
void_t
を使用したイディオムをサポートするためのツールキットの提案。これは採択されていないが、ツールとしては有用なので適時使用するとよいだろう
- Detection Idiom - yohhoyの日記
お断り
この記事の内容は、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_equal
はstd::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
deque
,forward_list
,list
,map
,multimap
,set
,multiset
,unordered_map
,unordered_multimap
,unordered_set
,unordered_multiset
参照
- N4002 Cleaning-up
noexcept
in the Library - N4227 Cleaning-up
noexcept
in the Library (Rev 2) - N4258 Cleaning-up
noexcept
in the Library (Rev 3)
お断り
この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。
C++1z 標準イテレータ全般とarrayの変更操作にconstexprを追加
C++1zでは、以下の機能にconstexpr
が付きます。
<iterator>
std::advance()
関数std::distance()
関数std::next()
関数std::prev()
関数std::reverse_iterator
クラスのメンバ関数、非メンバ関数すべてstd::move_iterator
クラスのメンバ関数、非メンバ関数すべて- コンテナに対する
std::begin()
、std::end()
関数 - コンテナに対する
std::rbegin()
、std::rend()
関数 - 配列に対する
std::rbegin()
、std::rend()
関数 std::initializer_list
に対するstd::rbegin()
、std::rend()
関数std::crbegin()
、std::crend()
関数
<array>
std::array
の以下のメンバ関数begin()
、end()
rbegin()
、rend()
cbegin()
、cend()
crbegin()
、crend()
operator[ ]
at()
front()
back()
data()
参照
お断り
この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。
C++1z 連想コンテナ用のデフォルトの順序付け
この機能はC++1z入りが取り消されました。
map
やset
のキーとなる型はデフォルトで、operator<
で比較できる必要があります。連想コンテナのキーにできるようにするために、ユーザー定義型にoperator<
を定義することもありますが、その比較演算を連想コンテナ以外の用途では使わないことが多々あります。
連想コンテナ用の比較演算とそうでない比較演算で用途を分けられるようにするために、C++1zではdefault_order
という中間インタフェースの型が定義されます。
// <functional> namespace std { template <class T = void> struct default_order { using type = std::less<T>; }; template <class T = void> using default_order_t = typename default_order<T>::type; }
そして、連想コンテナの比較演算関数オブジェクトは、std::less
クラスを直接使用するのではなく、default_order
を介してstd::less
を使用するよう変更されます。これによるABIの破壊はありません。
// <map> namespace std { template <class Key, class T, class Compare = default_order_t<Key>, class Allocator = allocator<pair<const Key, T>>> class map; template <class Key, class T, class Compare = default_order_t<Key>, class Allocator = allocator<pair<const Key, T>>> class multimap; }
// <set> namespace std { template <class Key, class Compare = default_order_t<Key>, class Allocator = allocator<Key>> class set; template <class Key, class Compare = default_order_t<Key>, class Allocator = allocator<Key> class multiset; }
// <queue> namespace std { template <class T, class Container = vector<T>, class Compare = default_order_t<typename Container::value_type>> class priority_queue; }
型を連想コンテナのキーとして使用できるようにするためだけに順序付けしたい場合は、default_order
を特殊化して順序判定用の関数オブジェクトを使用するよう指定することになります。
namespace sales { struct account { int id; std::string name; }; // 連想コンテナで順序付けするための関数オブジェクト struct order_accounts { bool operator()(const Account& lhs, const Account& rhs) const { return lhs.id < rhs.id; } }; } namespace std { // sales::account型については、 // sales::order_account関数オブジェクトでキーの順序付けをする template<> struct default_order<sales::account> using type = sales::order_accounts; }; } int main() { std::set<sales::account> s; }
教科書的な説明がむずかしくなるので、ちょっと困りますね。advancedな用途と考えて、初心者向けには従来の方法を説明するのがよさそうです。
参照
お断り
この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。
C++1z 連想コンテナの接合
C++1zでは、2つの連想コンテナを接合(splice)する機能が入ります。対象は、map
、set
、unordered_map
、unordered_set
とそれらのmulti版すべてです。
まず、特定の要素を抽出する機能として、extract()
メンバ関数が追加されます。
node_type extract(const_iterator position);
node_type extract(const key_type& x);
要素を抽出する操作にともない、すべての連想コンテナに、node_type
という入れ子型が追加されます。その実装となる型は、標準ライブラリの機能としては定義されず、連想コンテナの要件としてnode_type
ができることが定義されます。
次に、抽出した要素をほかのコンテナに挿入する機能として、insert()
メンバ関数にnode_type
を受け取るオーバーロードが追加されます。
insert_return_type insert(node_type&& nh); iterator insert(const_iterator hint, node_type&& nh);
この挿入機能のために、非multi連想コンテナにinsert_return_type
という入れ子型が追加されます。multi連想コンテナはiterator
を返します。insert_return_type
型も標準ライブラリの機能としてではなく、要件として定義されます。この型は、以下のメンバ変数を持ちます:
bool inserted; // 挿入が成功したか X::iterator position; // 挿入した要素を指すイテレータ X::node_type node; // 指定されたノード
最後に、2つの連想コンテナをまるまる接合するmerge()
メンバ関数が追加されます。このメンバ関数は、multi版と非multi版の両方のコンテナを受け取れます。たとえばmap
の場合は、以下のメンバ関数を持ちます。
template <class C2> void merge(map<Key, T, C2, Allocator>& source); template <class C2> void merge(map<Key, T, C2, Allocator>&& source); template <class C2> void merge(multimap<Key, T, C2, Allocator>& source); template <class C2> void merge(multimap<Key, T, C2, Allocator>&& source);
サンプルコード : 抽出と挿入
#include <map> #include <string> int main() { std::map<int, std::string> src {{1,"one"}, {2,"two"}, {3,"buckle my shoe"}}; std::map<int, std::string> dst {{3,"three"}}; dst.insert(src.extract(src.find(1))); // イテレータ版 dst.insert(src.extract(2)); // キー版 // 挿入先にすでに同一キーの要素がある場合は、挿入されない auto r = dst.insert(src.extract(3)); // src == {} // dst == {"one", "two", "three"} // r.position == dst.begin() + 2 // r.inserted == false // r.node == "buckle my shoe" }
サンプルコード : コンテナまるごとマージ
#include <set> int main() { std::set<int> src{1, 3, 5}; std::set<int> dst{2, 4, 5}; dst.merge(src); // srcをdstにマージ // マージできなかった要素はsrcに残る // src == {5} // dst == {1, 2, 3, 4, 5} }
参照
- LWG Issue #839 Maps and sets missing splice operation
- LWG Issue #1041 Add associative/unordered container functions that allow to extract elements
- N3586 Splicing Maps and Sets
- N3645 Splicing Maps and Sets (Revision 1)
- P0083R0 Splicing Maps and Sets (Revision 2)
- P0083R1 Splicing Maps and Sets (Revision 3)
- P0083R2 Splicing Maps and Sets (Revision 4)
- P0083R3 Splicing Maps and Sets (Revision 5)
お断り
この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。
C++1z mapとunordered_mapに、挿入失敗時の動作を規定した新たなメンバ関数を追加
一意なキーを持つstd::map
とstd::unordered_map
に対して、2種類のメンバ関数が追加されます。対象には、std::multimap
やstd::unordered_multimap
および集合コンテナは含みません。
try_emplace()
メンバ関数 : 挿入失敗時に、与えられたパラメータargs...
を変更しないことが保証されるinsert_or_assign()
メンバ関数 : 挿入に失敗したら上書きする (m[key] = value;
と同じような動作)
元々はinsert()
とemplace()
の仕様を修正することが考えられていましたが、議論の結果、別名の関数が定義されることになりました。
std::map<std::string, std::unique_ptr<Foo>> m; m["foo"] = nullptr; std::unique_ptr<Foo> p(new Foo); auto res = m.try_emplace("foo", std::move(p)); assert(p); // pは有効
insert_or_assign()
は、これまでと同様にpair<iterator, bool>
を戻り値として返します。挿入に失敗して上書き操作をするときに、second == false
となります。
宣言
// mapとunordered_mapのメンバ関数 template <class... Args> pair<iterator, bool> try_emplace(const key_type& k, Args&&... args); template <class... Args> pair<iterator, bool> try_emplace(key_type&& k, Args&&... args); template <class... Args> iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args); template <class... Args> iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args); template <class M> pair<iterator, bool> insert_or_assign(const key_type& k, M&& obj); template <class M> pair<iterator, bool> insert_or_assign(key_type&& k, M&& obj); template <class M> iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj); template <class M> iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj);
参照
- N3873 Improved insertion interface for unique-key maps
- N4006 An improved
emplace()
for unique-key maps - N4279 Improved insertion interface for unique-key maps (Revision 2.3)
お断り
この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。