すごいユーザ定義リテラルたのしく遊ぼう
中3女子です。
今回は、みんな大好きユーザ定義リテラルについて。
ユーザ定義リテラルは、値(整数、不動小数点数、文字列)に対するプログラマブルなサフィックスを定義することができる機能である。
例えば糖衣構文や単位を持った値を扱うために用いられる。
{
constexpr auto duration = 4_mins + 33_secs;
}
ユーザ定義リテラルはいらない子か
ところで、このユーザ定義リテラルは、方々で微妙な子扱いされている。
というのも、そもそもユーザ定義リテラルは直観的な記述を可能とするものであるはずなのだが、その用法には一見非直観的な落とし穴がある。
例えばユーザ定義リテラルの識別子は、アンダースコアで始めなければならない。
なぜなら、アンダースコアで始まらないサフィックスは、すべて予約されているからだ。
プログラマがアンダースコアで始まらないユーザ定義リテラルを定義してもill-formedにはならないが、それはインクルードガードの為に__FILENAME_H__といったマクロを定義するのと同じで、未定義である。
ゆえに、ユーザ定義リテラルをグローバル名前空間に定義することもできない。
なぜならグローバル名前空間において、アンダースコアで始まる名前は、すべて予約されているからだ。
また、名前空間で定義されたユーザ定義リテラルを直観的に使うためには、using宣言やusingディレクティブによってスコープに導入しなければならない。
だから実際には、ユーザ定義リテラルを使うには以下のようにする必要がある。
-
- 我々がかつて望んだユーザ定義リテラル
constexpr chrono::minutes operator"" mins(unsigned long long); constexpr chrono::seconds operator"" secs(unsigned long long); { constexpr auto duration = 4mins + 33secs; }
-
- 我々に与えられたユーザ定義リテラル
namespace ns { constexpr chrono::minutes operator"" _mins(unsigned long long); constexpr chrono::seconds operator"" _secs(unsigned long long); } { using ns::operator"" _mins; using ns::operator"" _secs; constexpr auto duration = 4_mins + 33_secs; }
もちろん、C++の厳密さを愛する人は下のように書くほうが好ましいと思うかも知れない。
しかしながら、ユーザ定義リテラルは元々糖衣構文の色合いが濃い機能であるから、上のような書き方が完全に未定義であるのは明らかに直観的でない。
これは例えるなら、using namespace std; と書く(当然好ましくない記述だが、未定義ではない)のが未定義であるようなものだ。
記述の問題だけではない。
ユーザ定義リテラルの定義は、シグネチャがきわめて限定的で、自由度が低いから言語フリークが遊ぶことができない。
(もちろん僕は言語フリークではない)
そのシグネチャにしても、文字列リテラルをテンプレート引数として受け取ることができないなど、片手落ち感がある。
(これについては N3599 で提案されているので、C++1yでは改善されるかも知れない)
こうして見るとユーザ定義リテラルは、中途半端で使い所に乏しく、応用の開拓も難しい、必要とされない子のように見える。
しかしながら人間は、自由を希求しながらも与えられた不自由を愉しむという生物である。
ユーザ定義リテラルはたしかに機能不自由な子ではあるが、どんなものにもすべからく使いようはあるべきだ。
そこで今回は、中3女子が本当に有用で正しくて分かりやすいユーザ定義リテラルの使い方を書くこととする。
複素数リテラルと有理数リテラル
#ifndef SPROUT_COMPLEX_UDL_HPP #define SPROUT_COMPLEX_UDL_HPP #include <sprout/config.hpp> #include <sprout/complex/complex.hpp> #if SPROUT_USE_USER_DEFINED_LITERALS namespace sprout { namespace udl { namespace complex { // // _i // inline SPROUT_CONSTEXPR sprout::complex<double> operator"" _i(long double x) { return sprout::complex<double>(0, x); } // // _if // _iF // inline SPROUT_CONSTEXPR sprout::complex<float> operator"" _if(long double x) { return sprout::complex<float>(0, x); } inline SPROUT_CONSTEXPR sprout::complex<float> operator"" _iF(long double x) { return sprout::complex<float>(0, x); } // // _il // _iL // inline SPROUT_CONSTEXPR sprout::complex<long double> operator"" _il(long double x) { return sprout::complex<long double>(0, x); } inline SPROUT_CONSTEXPR sprout::complex<long double> operator"" _iL(long double x) { return sprout::complex<long double>(0, x); } } // namespace complex using sprout::udl::complex::operator"" _i; using sprout::udl::complex::operator"" _if; using sprout::udl::complex::operator"" _iF; using sprout::udl::complex::operator"" _il; using sprout::udl::complex::operator"" _iL; } // namespace udl using sprout::udl::complex::operator"" _i; using sprout::udl::complex::operator"" _if; using sprout::udl::complex::operator"" _iF; using sprout::udl::complex::operator"" _il; using sprout::udl::complex::operator"" _iL; } // namespace sprout #endif // #if SPROUT_USE_USER_DEFINED_LITERALS #endif // #ifndef SPROUT_COMPLEX_UDL_HPP
#ifndef SPROUT_RATIONAL_UDL_HPP #define SPROUT_RATIONAL_UDL_HPP #include <sprout/config.hpp> #include <sprout/rational/rational.hpp> #if SPROUT_USE_USER_DEFINED_LITERALS namespace sprout { namespace udl { namespace rational { // // _r // inline SPROUT_CONSTEXPR sprout::rational<int> operator"" _r(unsigned long long x) { return sprout::rational<int>(x); } // // _rl // _rL // inline SPROUT_CONSTEXPR sprout::rational<long> operator"" _rl(unsigned long long x) { return sprout::rational<long>(x); } inline SPROUT_CONSTEXPR sprout::rational<long> operator"" _rL(unsigned long long x) { return sprout::rational<long>(x); } // // _rll // _rLL // inline SPROUT_CONSTEXPR sprout::rational<long long> operator"" _rll(unsigned long long x) { return sprout::rational<long long>(x); } inline SPROUT_CONSTEXPR sprout::rational<long long> operator"" _rLL(unsigned long long x) { return sprout::rational<long long>(x); } } // namespace rational using sprout::udl::rational::operator"" _r; using sprout::udl::rational::operator"" _rl; using sprout::udl::rational::operator"" _rL; using sprout::udl::rational::operator"" _rll; using sprout::udl::rational::operator"" _rLL; } // namespace udl using sprout::udl::rational::operator"" _r; using sprout::udl::rational::operator"" _rl; using sprout::udl::rational::operator"" _rL; using sprout::udl::rational::operator"" _rll; using sprout::udl::rational::operator"" _rLL; } // namespace sprout #endif // #if SPROUT_USE_USER_DEFINED_LITERALS #endif // #ifndef SPROUT_RATIONAL_UDL_HPP
#include <iostream> #include <sprout/complex.hpp> int main() { using namespace sprout::udl::complex; constexpr auto t = (1.5+0.5_i) / (1.5-0.5_i); std::cout << t.real() << "+" << t.imag() << "i" << std::endl; }
-
- 出力
0.8+0.6i
もしユーザ定義リテラルを使わなければ、(1.5+0.5_i) / (1.5-0.5_i) の代わりに、
constexpr auto t = sprout::complex<double>(1.5, 0.5) / sprout::complex<double>(1.5, -0.5);
などと書かなければならない。
#include <iostream> #include <sprout/rational.hpp> int main() { using namespace sprout::udl::rational; constexpr auto t = 8/36_r * 3; std::cout << t << std::endl; }
-
- 出力
2/3
UUIDリテラル
ユーザ定義リテラルは、当然ながら文字列リテラルに対して定義することができる。
もし第三者によって動的に入力された文字列を変換する場合は、パーズして入力の誤りをチェックし、ケースに応じて必要なエラーハンドリングを行わなければならない。
しかしながらプログラマ自身がハードコーディングする場合は、単なるマジックナンバーと同様に、簡潔に書けたほうがよい。
Sproutではこのために、UUIDリテラルを提供している。
#ifndef SPROUT_UUID_UDL_HPP #define SPROUT_UUID_UDL_HPP #include <sprout/config.hpp> #include <sprout/uuid/uuid.hpp> #if SPROUT_USE_USER_DEFINED_LITERALS #include <cstddef> #include <type_traits> #include <sprout/string.hpp> #include <sprout/uuid/string_generator.hpp> #include <sprout/uuid/name_generator.hpp> #include <sprout/ctype/functor.hpp> #include <sprout/container/functions.hpp> #include <sprout/container/metafunctions.hpp> #include <sprout/range/algorithm/equal.hpp> #include <sprout/range/ptr_range.hpp> namespace sprout { namespace uuids { namespace detail { template<typename T> inline SPROUT_CONSTEXPR sprout::basic_string<T, 3> dns_token(); template<> inline SPROUT_CONSTEXPR sprout::basic_string<char, 3> dns_token<char>() { return sprout::to_string("dns"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<wchar_t, 3> dns_token<wchar_t>() { return sprout::to_string(L"dns"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<char16_t, 3> dns_token<char16_t>() { return sprout::to_string(u"dns"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<char32_t, 3> dns_token<char32_t>() { return sprout::to_string(U"dns"); } template<typename T> inline SPROUT_CONSTEXPR sprout::basic_string<T, 3> url_token(); template<> inline SPROUT_CONSTEXPR sprout::basic_string<char, 3> url_token<char>() { return sprout::to_string("url"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<wchar_t, 3> url_token<wchar_t>() { return sprout::to_string(L"url"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<char16_t, 3> url_token<char16_t>() { return sprout::to_string(u"url"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<char32_t, 3> url_token<char32_t>() { return sprout::to_string(U"url"); } template<typename T> inline SPROUT_CONSTEXPR sprout::basic_string<T, 3> oid_token(); template<> inline SPROUT_CONSTEXPR sprout::basic_string<char, 3> oid_token<char>() { return sprout::to_string("oid"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<wchar_t, 3> oid_token<wchar_t>() { return sprout::to_string(L"oid"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<char16_t, 3> oid_token<char16_t>() { return sprout::to_string(u"oid"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<char32_t, 3> oid_token<char32_t>() { return sprout::to_string(U"oid"); } template<typename T> inline SPROUT_CONSTEXPR sprout::basic_string<T, 4> x500_token(); template<> inline SPROUT_CONSTEXPR sprout::basic_string<char, 4> x500_token<char>() { return sprout::to_string("x500"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<wchar_t, 4> x500_token<wchar_t>() { return sprout::to_string(L"x500"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<char16_t, 4> x500_token<char16_t>() { return sprout::to_string(u"x500"); } template<> inline SPROUT_CONSTEXPR sprout::basic_string<char32_t, 4> x500_token<char32_t>() { return sprout::to_string(U"x500"); } template<typename Range> inline SPROUT_CONSTEXPR sprout::uuids::md5_name_generator uuid3_impl(Range const& rng) { typedef typename std::decay<typename sprout::containers::value_type<Range>::type>::type value_type; typedef sprout::ctypes::nocase_equal_to<value_type> predicate_type; return sprout::range::equal(rng, sprout::uuids::detail::dns_token<value_type>(), predicate_type()) ? sprout::uuids::make_uuid3_dns() : sprout::range::equal(rng, sprout::uuids::detail::url_token<value_type>(), predicate_type()) ? sprout::uuids::make_uuid3_url() : sprout::range::equal(rng, sprout::uuids::detail::oid_token<value_type>(), predicate_type()) ? sprout::uuids::make_uuid3_oid() : sprout::range::equal(rng, sprout::uuids::detail::x500_token<value_type>(), predicate_type()) ? sprout::uuids::make_uuid3_x500() : sprout::uuids::make_uuid3(sprout::uuids::make_uuid(sprout::begin(rng), sprout::end(rng))) ; } template<typename Range> inline SPROUT_CONSTEXPR sprout::uuids::sha1_name_generator uuid5_impl(Range const& rng) { typedef typename std::decay<typename sprout::containers::value_type<Range>::type>::type value_type; typedef sprout::ctypes::nocase_equal_to<value_type> predicate_type; return sprout::range::equal(rng, sprout::uuids::detail::dns_token<value_type>(), predicate_type()) ? sprout::uuids::make_uuid5_dns() : sprout::range::equal(rng, sprout::uuids::detail::url_token<value_type>(), predicate_type()) ? sprout::uuids::make_uuid5_url() : sprout::range::equal(rng, sprout::uuids::detail::oid_token<value_type>(), predicate_type()) ? sprout::uuids::make_uuid5_oid() : sprout::range::equal(rng, sprout::uuids::detail::x500_token<value_type>(), predicate_type()) ? sprout::uuids::make_uuid5_x500() : sprout::uuids::make_uuid5(sprout::uuids::make_uuid(sprout::begin(rng), sprout::end(rng))) ; } } // namespace detail namespace udl { // // _uuid // inline SPROUT_CONSTEXPR sprout::uuids::uuid operator"" _uuid(char const* s, std::size_t size) { return sprout::uuids::make_uuid(s, s + size); } inline SPROUT_CONSTEXPR sprout::uuids::uuid operator"" _uuid(wchar_t const* s, std::size_t size) { return sprout::uuids::make_uuid(s, s + size); } inline SPROUT_CONSTEXPR sprout::uuids::uuid operator"" _uuid(char16_t const* s, std::size_t size) { return sprout::uuids::make_uuid(s, s + size); } inline SPROUT_CONSTEXPR sprout::uuids::uuid operator"" _uuid(char32_t const* s, std::size_t size) { return sprout::uuids::make_uuid(s, s + size); } // // _uuid3 // inline SPROUT_CONSTEXPR sprout::uuids::md5_name_generator operator"" _uuid3(char const* s, std::size_t size) { return sprout::uuids::detail::uuid3_impl(sprout::range::make_ptr_range(s, size)); } inline SPROUT_CONSTEXPR sprout::uuids::md5_name_generator operator"" _uuid3(wchar_t const* s, std::size_t size) { return sprout::uuids::detail::uuid3_impl(sprout::range::make_ptr_range(s, size)); } inline SPROUT_CONSTEXPR sprout::uuids::md5_name_generator operator"" _uuid3(char16_t const* s, std::size_t size) { return sprout::uuids::detail::uuid3_impl(sprout::range::make_ptr_range(s, size)); } inline SPROUT_CONSTEXPR sprout::uuids::md5_name_generator operator"" _uuid3(char32_t const* s, std::size_t size) { return sprout::uuids::detail::uuid3_impl(sprout::range::make_ptr_range(s, size)); } // // _uuid5 // inline SPROUT_CONSTEXPR sprout::uuids::sha1_name_generator operator"" _uuid5(char const* s, std::size_t size) { return sprout::uuids::detail::uuid5_impl(sprout::range::make_ptr_range(s, size)); } inline SPROUT_CONSTEXPR sprout::uuids::sha1_name_generator operator"" _uuid5(wchar_t const* s, std::size_t size) { return sprout::uuids::detail::uuid5_impl(sprout::range::make_ptr_range(s, size)); } inline SPROUT_CONSTEXPR sprout::uuids::sha1_name_generator operator"" _uuid5(char16_t const* s, std::size_t size) { return sprout::uuids::detail::uuid5_impl(sprout::range::make_ptr_range(s, size)); } inline SPROUT_CONSTEXPR sprout::uuids::sha1_name_generator operator"" _uuid5(char32_t const* s, std::size_t size) { return sprout::uuids::detail::uuid5_impl(sprout::range::make_ptr_range(s, size)); } } // namespace udl using sprout::uuids::udl::operator"" _uuid; using sprout::uuids::udl::operator"" _uuid3; using sprout::uuids::udl::operator"" _uuid5; } // namespace uuids namespace udl { namespace uuids { using sprout::uuids::udl::operator"" _uuid; using sprout::uuids::udl::operator"" _uuid3; using sprout::uuids::udl::operator"" _uuid5; } // namespace uuids using sprout::uuids::udl::operator"" _uuid; using sprout::uuids::udl::operator"" _uuid3; using sprout::uuids::udl::operator"" _uuid5; } // namespace udl using sprout::uuids::udl::operator"" _uuid; using sprout::uuids::udl::operator"" _uuid3; using sprout::uuids::udl::operator"" _uuid5; } // namespace sprout #endif // #if SPROUT_USE_USER_DEFINED_LITERALS #endif // #ifndef SPROUT_UUID_UDL_HPP
-
- UUIDリテラル
#include <iostream> #include <sprout/uuid.hpp> int main() { using namespace sprout::udl::uuids; constexpr auto t = "{75dea5d2-70b9-4cc8-9153-935176375cae}"_uuid; std::cout << "UUIDv" << t.version() << ": " << t << std::endl; }
-
- 出力
UUIDv4: 75dea5d2-70b9-4cc8-9153-935176375cae
見てのとおり、UUIDオブジェクトをリテラルとして記述することができる。
面倒な文字列のパーズは _uuid がコンパイル時に行ってくれる。
UUIDリテラルはこれだけではない。
UUIDv3とUUIDv5は、ベースとなる名前空間識別子UUID(C++でいうnamespaceとは違う)とデータ列のMD5/SHA-1を計算することでUUIDを算出する。
UUIDv3やUUIDv5の仕様に従ってUUIDをリテラルとして記述するには以下のようにする。
-
- UUIDv5リテラル
#include <iostream> #include <sprout/uuid.hpp> int main() { using namespace sprout::udl::uuids; constexpr auto t = "DNS"_uuid5("boost.org"); std::cout << "UUIDv" << t.version() << ": " << t << std::endl; }
-
- 出力
UUIDv5: 0043f363-bbb4-5369-840a-322df6ec1926
ここではRFC 4122で予約されているDNS namespace UUIDとドメイン名"boost.org"を使ってUUIDv5を生成している。
もちろんSHA-1ハッシュの計算などは内部でコンパイル時に行われている。
ちなみに"DNS"の部分には、他の予約されたトークンの他に、任意のUUID文字列を記述することができる。
ユーザ定義リテラルに後置で括弧が付いているのは一見奇妙に思えるかも知れないが、なんということはない。
_uuid5 は、単に"boost.org"のような文字列を引数としてUUIDオブジェクトを返すジェネレータ(関数オブジェクト)を返しているだけである。
これをユーザ定義リテラルなしで書くと以下のようになる。
constexpr auto t = sprout::uuids::sha1_name_generator(sprout::uuids::namespace_dns_uuid())("boost.org");
インデックス列リテラル
Sproutではこのほかに、IndexTupleイディオムで用いるインデックス列を記述するユーザ定義リテラルがある。
#ifndef SPROUT_INDEX_TUPLE_UDL_HPP #define SPROUT_INDEX_TUPLE_UDL_HPP #include <sprout/config.hpp> #include <sprout/index_tuple/index_tuple.hpp> #if SPROUT_USE_USER_DEFINED_LITERALS #include <type_traits> #include <sprout/index_tuple/make_index_tuple.hpp> namespace sprout { namespace detail { template<typename IntType, char... Chars> struct digits_to_int; template<typename IntType, char C> struct digits_to_int<IntType, C> : public std::integral_constant<IntType, IntType(C - 48)> {}; template<typename IntType, char Head, char... Tail> struct digits_to_int<IntType, Head, Tail...> : public std::integral_constant<IntType, 10 * IntType(Head - 48) + sprout::detail::digits_to_int<IntType, Tail...>::value> {}; } // namespace detail // // indexes_result // template<char... Chars> struct indexes_result : public sprout::make_index_tuple<sprout::detail::digits_to_int<sprout::index_t, Chars...>::value> {}; // // uindexes_result // template<char... Chars> struct uindexes_result : public sprout::make_uindex_tuple<sprout::detail::digits_to_int<sprout::uindex_t, Chars...>::value> {}; namespace udl { namespace indexes { // // _indexes // template<char... Chars> SPROUT_CONSTEXPR typename sprout::indexes_result<Chars...>::type operator"" _indexes() { return sprout::indexes_result<Chars...>::make(); } // // _uindexes // template<char... Chars> SPROUT_CONSTEXPR typename sprout::uindexes_result<Chars...>::type operator"" _uindexes() { return sprout::uindexes_result<Chars...>::make(); } } // namespace indexes using sprout::udl::indexes::operator"" _indexes; using sprout::udl::indexes::operator"" _uindexes; } // namespace udl using sprout::udl::indexes::operator"" _indexes; using sprout::udl::indexes::operator"" _uindexes; } // namespace sprout #endif // #if SPROUT_USE_USER_DEFINED_LITERALS #endif // #ifndef SPROUT_INDEX_TUPLE_UDL_HPP
例えば
10_indexes
とすれば、index_tuple<0, 1, 2, 3, 4, 5, 6, 7, 8, 9> なるオブジェクトが生成される。
これは、ユーザ定義リテラルが template
もっとも、このような形式でテンプレートパラメータパックとして受け取ることができるのは整数もしくは浮動小数点リテラルだけであることに注意されたし。
文字列リテラルをテンプレートパラメータパックとして受け取ることは、C++11では不可能である。
C++1yのユーザ定義リテラル
もしも N3599 の提案が採択されたならば、
"Hello, world!"_mpl_string /* same as: mpl::string<'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!'>() */
というような記述が可能になる。
これは例えば、完全な静的型チェック付のprintfが、自然な構文で、マクロを一切使わず実装できることを示している。
同様のことはconstexprを使ったとしてもマクロなしには実装し得なかったから、もしこれがC++1yにおいて実現すれば、ユーザ定義リテラルは唯一無二の役割を担うことになる。
そのときユーザ定義リテラルは最早いらない子ではなく、闇の勢力の虜となって、フリーク達に弄ばれる玩具となるだろう。
だからN3599には、大いに期待したいものである。