リテラル型クラスの条件、および「中3女子でもわかる constexpr」の訂正
中3女子です。
これはおよそ一年半前(2011/12/3)に公開したスライドだが、この中の記述に規格上の誤りがあるので、今更だがここで訂正しておく。
問題は p.20〜『◆リテラル型クラスの条件』の項目だ。
この中の記述は、C++11 の規格策定における議論段階の文言を元にしており、これは後に文言が改訂されたため、
スライド中の記述と現行 C++11 の規格とで齟齬が生じてしまっている。
ちなみに、現行 C++11 の規格では、リテラル型クラスの条件はこのようになっている。
-
- N3337 § 3.9 Types - 10 より抜粋
― a class type (Clause 9) that has all of the following properties:
― it has a trivial destructor,
― every constructor call and full-expression in the brace-or-equal-initializers for non-static data members (if any) is a constant expression (5.19),
― it is an aggregate type (8.5.1) or has at least one constexpr constructor or constructor template that is not a copy or move constructor, and
― all of its non-static data members and base classes are of literal types.
正誤表
以下に、スライド中の記述について正誤表を示す。
イタリック部分が訂正箇所である。
◆リテラル型クラスの条件
-
- p.20
【誤】
・コンパイラの要求
- trivial コピーコンストラクタを持つ
- 非 trivial ムーブコンストラクタを持たない
- trivial デストラクタを持つ
- trivial デフォルトコンストラクタか、コピーでもムーブでもない constexpr コンストラクタを持つ
- static データメンバと基底クラスは、全てリテラル型である
【正】
- trivial デストラクタを持つ
- 非 static データメンバのメンバ初期化子の式全体とコンストラクタ呼出は、すべて定数式である
- アグリゲートであるか、またはコピーでもムーブでもない constexpr コンストラクタを一つ以上持つ
- 非 static データメンバと基底クラスは、全てリテラル型である
-
- p.21
【誤】
・プログラマの「べからず」
- 仮想関数や仮想基底クラスを書かない
- ユーザ定義コピーコンストラクタを書かない
- (delete もしない)
- ユーザ定義ムーブコンストラクタを書かない
- (delete するのはよい)
- ユーザ定義デストラクタを書かない
- ユーザ定義デフォルトコンストラクタを書くなら原則 constexpr にする
- (デフォルトコンストラクタが trivial でも constexpr でもない場合、別の constexpr コンストラクタを書く)
- リテラル型以外の非 static データメンバや基底クラスを使わない
【正】
・プログラマの「べからず」
- 仮想関数や仮想基底クラスを書かない
- ユーザ定義デストラクタを書かない
- 非定数式のメンバ初期化子を書かない
- ユーザ定義デフォルトコンストラクタを書くなら原則 constexpr にする
- (デフォルトコンストラクタが trivial でも constexpr でもない場合、別の constexpr コンストラクタを書く)
- リテラル型以外の非 static データメンバや基底クラスを使わない
解説
大きな改訂部分は、非 trivial コピー/ムーブコンストラクタを書けるようになったことである。
struct LiteralType { /* ユーザ定義コピーコンストラクタを持っていてよい */ constexpr LiteralType(LiteralType const&) { } /* ユーザ定義ムーブコンストラクタを持っていてよい */ constexpr LiteralType(LiteralType&&) { } };
また、C++11 から標準化されたメンバ初期化子についての記述が追加されている。
struct LiteralType { /* メンバ初期化子は、定数式でなければならない */ int t = -1; // 非 static データメンバ };
ちなみに、trivial デフォルト/コピー/ムーブコンストラクタは暗黙で constexpr 指定される。
もちろん、defaulted された場合も同様である。
class LiteralType { int t; public: explicit constexpr LiteralType(int t) : t(t) { } /* trivial コンストラクタは暗黙で constexpr である */ LiteralType() = default; LiteralType(LiteralType const&) = default; LiteralType(LiteralType&&) = default; }; constexpr auto t0 = LiteralType(-1); constexpr auto t1 = LiteralType(); constexpr auto t2 = LiteralType(t1); constexpr auto t3 = LiteralType(LiteralType());
これについて、処理系のバグも付記しておく。
非 trivial constexpr ムーブコンストラクタを持つ(または、そのような基底クラスや非 static データメンバを持つ)クラスは、規格上リテラル型である。
しかし GCC4.8.0 - 4.8.1 においてこのようなクラスを constexpr 関数の値渡しの引数に二段以上渡すと、コンパイルエラーになる。
struct Z { Z() = default; Z(Z const&) = default; constexpr Z(Z&&) {} /* non-trivial (constexpr) move ctor */ }; template<typename T> constexpr int fn0(T v) { return 0; } template<typename T> constexpr int fn (T v) { return fn0(v); } constexpr auto t0 = fn0(Z()); // OK! constexpr auto t = fn (Z()); // error! (GCC 4.8.1, -std=c++11)
なお、Clang3.2 - 3.3 および GCC4.7 では、このコードは問題なくコンパイルされる。
これについては GCC にバグ報告をしておいた。
【Bug 57901 – [4.8/4.9 Regression] Cannot call-by-value such that class has non-trivial (constexpr) move constructor】
今更だけど Boost.勉強会 #12 大阪でした
もう三週間が経ってアレですが、Boost.勉強会 #12 で発表してきました。
Boost.勉強会 #12 大阪 - boostjp
発表
発表タイトルは『constexpr中3女子テクニック―実践と濫用そしてC++14へ』です。
constexpr によって、コンパイル時の処理をどこまでどのように記述できるのかということを、拙作の Sprout ライブラリ の実例と実装テクニックを紹介しつつ解説しました。
また、次期標準の C++14 で constexpr がどのように進化するか、またどのような問題点が残っているかについても併せて紹介しました。
C++ に精通している皆さんには正直物足りない内容だったかもしれませんが、こういう機会に一人でも多くの方に constexpr に興味をもったり理解を深めて戴けたなら幸いです。
ところで、資料をつくる段階で少し内容を詰め込みすぎかと思って優先度の低い項目を削ったりしたのですが、それでもスライドにして 138枚。
案の定発表時間を大幅にオーバーしてしまい、主催者の遥佐保さんをはじめ皆さんにご迷惑おかけして申しわけありませんでした。
Boost.勉強会参加が丸一年以上振りということもあって、すこし気張り過ぎたかなと思います。
できればこまめに発表したいところなのですが、大都会奥地在住の悲しさで、東京や大阪といった田舎なんぞには頻繁に出かけて行きづらいという事情もあり悩ましいです。
他の方の発表の雑感
- C++で作るWEBアプリケーション
Emscripten すごいですね。
こういうのが出てくるのも、GCCと 異なり極度にモジュール化された設計である LLVM ならではだと思います。
- C++初心者のためのBoost.MPL入門
珍しくまともに Boost のライブラリを取り上げた発表。
テンプレートメタプログラミングは非常によく使います。
ただ発表で言及されていたように「古いライブラリ」感があるのは、歴史のある枯れたライブラリであるという以外に、C++11 では Variadic Templates の導入や標準ライブラリの整備などでメタ関数が非常に書きやすくなったために、Boost.MPL を使わなくても自分で簡単に実装できるようになってしまった、という理由があるように感じます。
非常に多くの機能が用意されているけど実際に使われるのは割と一部、という点には何というかシンパシーを感じますね!
Boost.MPL の真の価値の一つは、メタ関数の概念とテンプレートメタプログラミングの応用範囲を示して広く浸透させたことにあると思います。
- boost.multiprecisionと適応型無誤差演算
まともに Boost のライブラリを取り上げた発表その2。
お好み焼きのせいで前半聴けず済みませんでした……。
任意精度演算については、いずれコンパイル時に出来るよう実装したいと思っていたため参考になりました。
- Allocators@C++11
普段 constexpr ばかり触っているのでアロケータ周りにはほとんど触れていなかったのですが、さすが C++11 だけあって色々な進化を感じました。
相変わらず C++14 でも constexpr で動的メモリを扱えないクソ言語機能なのはどうにかなりませんかね。
- 君はまだ、本当のプリプロセスを知らない
本当のプリプロセスを見た。
トークンの判定を使ってマクロで疑似構文のようなものをつくるのは面白かったです。
可変長マクロも導入されて大分書きやすくなった感があるので、近々魔クロプログラミングで何か実装したいです。
- C++コミュニティを作る
「みんなもっとオープンに世界にコミットしていこうぜ」という提言。
皆さんもっと Boost や他のプロジェクトにバグ報告したり PullRequest したり公開したり殴り合ったりしましょう。
C++11の糞仕様と戦ってアクセッサをconstexprにする
中3女子です。
C++11が、C++03およびそれ以前に対して明らかに優れていることは今更言うまでもない。
しかしながら、C++11にも恥ずべき糞仕様は存在する。
その糞の代表例が、constexprメンバ関数が暗黙でconst修飾されることである。
これによってどんな問題が生ずるかを、まずは見てゆく。
constexprメンバ関数が暗黙でconst修飾される糞仕様の問題
まずは、適当な値のホルダクラスを考える。
template<typename T> struct Holder { T value; };
Holderはリテラルクラスの要件を満たすから、当然定数式として扱える。
constexpr auto t = Holder<int>{ 100 }; constexpr int i = t.value;
では、下記のコードは合法か?
constexpr int i = Holder<int>{ 100 }.value; // データメンバを参照
勿論イエス。
prvalue(一時オブジェクト)やrvalue参照からのデータメンバの参照は、定数式になることが出来る。
ところで、std::optionalのようなクラスや、あるいはデータメンバを隠蔽したい場合は通常、アクセッサメンバ関数を書く。
setter/getterを別々に書く場合もあるが、optional::value()のように、保持するデータへの参照を返したほうがよい場合も多い。
よく知られているように、メンバ関数は*thisに対するconst修飾の有無によってオーバーロードすることができる。
つまり、以下のようになる。(全く隠蔽になってないのは、単純化のためなので無視戴きたい)
template<typename T> struct Holder2 { T v_; constexpr T& value() { return v_; } /* 非const版 */ constexpr T const& value() const { return v_; } /* const版 */ };
しかしながら、このコードはC++11においてill-formedである。
なぜならば、constexprメンバ関数が暗黙でconst修飾されるから、上記のコードは下記と同じに解釈される。
template<typename T> struct Holder2 { T v_; constexpr T& value() const { return v_; } /* Oops! 非const版……にならない */ constexpr T const& value() const { return v_; } /* const版 */ };
結果的に、返値型のみが異なるメンバ関数の定義であると見做され、コンパイルエラーになる。
C++11において、非const版とconst版のアクセッサメンバ関数を書くには、最も安易な方法は非const版をconstexpr指定しないことである。
template<typename T> struct Holder2 { T v_; T& value() { return v_; } /* 非const版 */ constexpr T const& value() const { return v_; } /* const版 */ };
これでめでたくwell-formedなクラスとなり、定数式としても扱える。
constexpr auto t = Holder2<int>{ 100 }; constexpr int i = t.value();
ところが、今度は別な問題が生ずる。それはprvalueやrvalue参照のオブジェクトからアクセッサを呼ぶ場合である。
constexpr int i = Holder2<int>{ 100 }.value(); // Oops! 非const版が呼ばれる
Holder2
非constなprvalueやrvalue参照からのメンバ関数呼び出しは、非const版が優先される。
そしてHolder2::value()の非const版はconstexprではない。
定数式が必要な文脈でのnon-constexpr関数の呼出はill-formedであるから、無残にもコンパイルエラーになる。
constexprという予約語の字面のせいで時々誤解が起こるが、const性が担保されるのは、評価が完了したコンパイル時定数のみである。
定数式評価中の文脈におけるオブジェクトのconst性とはまったく無縁である。
たとえコンパイル時であっても、非const rvalue/lvalue参照などの、あらゆるvalue categoryの式が扱われる。
constexpr変数が暗黙でconst修飾される(しかも constexpr const T と書くことが出来ない)のは、個人的に好みではないが、まだ妥当性のある仕様といえる。
コンパイル時定数は当然constであるのが自明だからだ。
しかしながら、constexprメンバ関数が暗黙でconst修飾されるのは論外である。
定数式の評価結果に対するconst性と、定数式評価中のconst性を取り違えた、致命的な失策の、腐敗した糞仕様と言わざるをえない。
アクセッサをconstexprにするには
糞仕様もまた仕様なり。
C++11がC++11である限り、その中で実装しなければならない。
ここで問題となっているのは、非staticメンバ関数である。
ならば、staticメンバ関数やフリー関数を使えばよい。
template<typename T> struct Holder3 { T v_; static constexpr T& value(Holder3& t) { return t.v_; } /* 非const lvalue参照版 */ static constexpr T&& value(Holder3&& t) { return static_cast<T&&>(value(t)); } /* 非const rvalue参照版 */ static constexpr T const& value(Holder3 const& t) { return t.v_; } /* const版 */ };
staticメンバ関数を使えば、lvalue参照版とrvalue参照版を分けることも容易である。
(rvalue参照はlvalueなので、static_castでT&&にforwardしている点に注意されたし)
ところで、このように自己のstaticメンバ関数を使ってデータの参照を得させる手法は、libstdc++などの典型的なstd::tupleの内部実装にも用いられている。
using H = Holder3<int>; constexpr int i = H::value(H{ 100 });
とはいえ、内部実装ならともかくユーザコードで使うにはこのように、いかにも冗長になってしまう。
template<typename T> constexpr T& value(Holder3<T>& t) { return Holder3<T>::value(t); } /* 非const lvalue参照版 */ template<typename T> constexpr T&& value(Holder3<T>&& t) { return static_cast<T&&>(value(t)); } /* 非const rvalue参照版 */ template<typename T> constexpr T const& value(Holder3<T> const& t) { return Holder3<T>::value(t); } /* const版 */
このようなフリー関数を追加すれば、
constexpr int i = value(Holder3<int>{ 100 });
このようにすっきり書くことができる。
C++erの間では昔から言われている、「メンバ関数を増やすよりもフリー関数を使うべき」という言説の根拠がこれでまた一つ増えた。
C++14
C++14では、constexprメンバ関数が暗黙でconst修飾されるという、C++11最悪の失敗であるこの忌まわしい糞仕様が削除される。
(N3652)
つまり、最初に書いたようなコードがそのままコンパイル出来る(はずである)。
template<typename T> struct Holder4 { T v_; constexpr T& value() { return v_; } /* 非const lvalue参照版 */ constexpr T&& value() && { return static_cast<T&&>(v_); } /* 非const rvalue参照版 */ constexpr T const& value() const { return v_; } /* const版 */ };
素晴らしい。
ともあれ、フリー関数が様々な点で優れていることには変わりないので、両方の選択肢があるならば、互換性も含めてフリー関数の方をより使うべきだと個人的には思う。
ところで、C++14のstd::optionalやstd::arrayなどのメンバ関数は、上記の仕様改善にも係わらず、非const版はconstexpr指定されていない。
これは、標準ライブラリはC++14においても、未だC++11時代のconstexprの仕様を念頭に設計されているということを示している。
標準ライブラリの仕様は、よほど先進的な部分を除いてコア言語に追従して改訂されることになる以上、仕方のないことだとも言える。
幸いなことに、C++11からC++14への大幅改修を見ても分かるように、constexprは標準化委員会においても大分スポットが当たっているように思える。
更に次期規格C++1yでの改善を大いに期待したい。
なお、C++14におけるconstexprについては、Boost.勉強会 #12で詳細に解説したいと思う。
すごいユーザ定義リテラルたのしく遊ぼう
中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には、大いに期待したいものである。
次期C++ではコンパイル時レイトレーシングやパーサコンビネ―タや音声合成が標準ライブラリで提供される
※これはエイプリルフール記事です
C++1yでは、レイトレーシングやパーサコンビネ―タや音声合成が、標準ライブラリに含まれることが決定している。
もちろんこれらはconstexprで実装され、コンパイル時に実行することができる。
しかしながら、C++11のconstexprではあまりに制限が多いため実装が困難である。
現在C++標準化委員会では、constexprの制限を大幅に緩和すべく提案がなされている。
言うまでもなくこれらの提案は、コンパイル時レイトレーシングのパーサコンビネ―タや音声合成を実装するためのものである。
その次のC++2z(仮)では、更にコンパイル時フォトンマッピングやコンパイル時ウェーブレット変換、コンパイル時動画編集などが追加される予定だという。
さて今回は、constexprの制限緩和を提案するN3597を翻訳してみた。
中3女子レベルの英語力によるものなので、著しい誤訳があれば指摘いただきたい。
原文:Relaxing constraints on constexpr functions
constexpr関数の制限緩和
概要
プログラミング言語の機能は、それらが直交しているほど便利で理解しやすく、また互いに自然に組み合わせることができる。
constexpr関数が現在抱えている、他の多くの言語機能との自然な組合せを妨げる数々の制限(例えば、forループ、変数の変更、例外など)のため、記述するのが難しくなっている。
これらの制限を回避する作業は、多くの場合、表現力を犠牲にする必要があり、プログラマのフラストレーションを引き起こす。
本稿では、それらを実行時C++コードのようによりシンプルで統一的にするために、constexpr関数定義の制限のほとんどを除去することを検討する。
慣用的なC++コードは、constexprキーワードの追加以外、非constexprな書き方から殆どまたはまったく変更することなく、constexpr関数内で許可されるであろう。
問題
以前の N3268 では、constexpr関数の本体は、次の形式にする必要があった
{ return expression; }
N3268 は、(7.1.5/3)を許可するようにルールを緩めた:
-
- 空文
- static_assert宣言
- typedef宣言と、クラスまたは列挙型を定義しないエイリアス宣言
- using宣言
- usingディレクティブ
- および、たった一つのreturn文
これらのconstexpr関数定義の制限事項は依然として非常に厳しく、またルールの緩和は結果的に教育を難しくし、それを正当化することにもなった。
非自明なconstexpr関数が複雑になると、もし既に純粋関数型的なインタフェースであったとしても、コードを構文上の制約の範囲内に収まるようにねじ曲げなければならないため、多くの人にとって馴染みのないコーディングスタイルしならざるをえない。
std::bitset
template <size_t N> bool bitset<N>::all() const noexcept { if (std::any_of(storage, storage + num_full_words, [] (word w) { return ~w; })) return false; if (num_full_words != num_words && storage[num_full_words] != last_word_mask) return false; return true; }
このコードはシンプルかつ慣用的であり、他のライブラリコンポーネントを使用することもできる。しかし、この関数のconstexpr版を作りたい場合、我々はそれを書き換える必要がある。
constexpr bool any_unset(word *it, word *end) { return it == end ? false : ~*it ? true : any_unset(it + 1, end); } template <size_t N> constexpr bool bitset<N>::all() const noexcept { return !any_unset(storage, storage + num_full_words) && (num_full_words == num_words || storage[num_full_words] == last_word_mask); }
この実装は、constexprの制限のいくつかに苦しんでいる:
代替
ポートランド(2012年10月)での議論では、単純なforループのサポートはconstexprのルールを十分緩和するための最小要件であることを確認している。この要件は、いくつかの方法によって達成することができる:
-
- constexprが必要とする関数型プログラミングスタイルとうまく相互作用するような、新しいループ構造の言語への追加。これはループ構造の欠如を直接解決するが、既存の言語構造に対するプログラマのフラストレーションを無くすことにはならない。
- 伝統的なC言語の構文の、最小限の機能セットの許可。ループの反復ごとに異なる動作をさせるためには、constexpr関数の評価の際に、ローカル変数の変更について最小限のサポートを必要とする。これは組込整数型Tについて、 for (T var = expr1; var != expr2; ++var) という形式に制限される可能性がある。
- range-based for をサポートする最小限の機能セットの許可。ループの複数反復が相互通信できるように、追加的な機構が必要となるだろう。このようなループをユーザ定義のイテレータ型と共に使用するのは、更なる言語の制限緩和なしにはできなかった。
- constexpr関数で使用される可能性があるC++全般についての、一貫した広範なサブセットの許可。
最初の選択肢は、constexpr部分と「それ以外のC++」部分において、C++言語を更に破壊するリスクがある。
2番目と3番目の選択肢は、定数式の評価にフロー制御と変数の変更を加えることの両方を必要とし、constexpr関数において今日より多くの恣意的な制限が見られるようになるだろう。
そこで我々は、最後の選択肢を詳細に検討し、forループのサポートに必要な範囲を超えて、実装のシンプルさを犠牲にすることなく使いやすさを改善するための、C++の適切なサブセットを模索することにした。
我々は、すべての主要な実装者が定数式中でのサポートを期待することが理的であるようなC++のサブセットとなるよう、細心の注意を払う必要がある。
さらに、翻訳時と実行時の区別を維持することは重要であり、翻訳環境でサポートできないコンストラクトの許可は避けねばならない(例えば、翻訳時のnewと実行時のdeleteに対応するには、重大な実装上の問題があるだろう)。
提案されたソリューション
constexprによる、ほぼ制限のないコンパイル時関数評価機構を推進する。
プログラミング言語Dではそのような機構の実装実績があり、それはポピュラーな機能である(この機能についてはドキュメントを参照)。
プログラマのモデルはシンプルになり、constexprによって彼らのコードはコンパイル時に実行することができるようになる。
定数式
C++の抽象マシンの規則に従って、次のものの評価が含まれていなければ、式は定数式である。
-
- 静的記憶域期間のオブジェクト(もしあれば、構築中のオブジェクト以外)の変更のような、グロバールから可視の副作用
- 動的メモリ割り当てや、不定な結果との比較、評価中に定数として作成されていないオブジェクトの左辺値から右辺値への変換など、翻訳時に評価できない表現。
- 型安全性を侵害したり、(例えばreinterpret_castによって)抽象マシンの基本記憶域を検査すること、またはインラインasmの使用など、可搬性のないコンストラクト
- 非constexpr関数の呼び出し、または
- 未定義動作。
その結果の値が完全に初期化され、テンポラリを指す任意の参照やポインタを含まず、または自動、動的、あるいはスレッド記憶域期間を持つオブジェクト。
C++11における関数呼び出しの置換(function invocation substitution)は、このモデルでは必要ない。constexpr関数の呼び出しではなくC++の抽象マシンによって通常どおり処理される。
実装のシンプルさに対する懸念のために、ラムダ式、スロー式、および非自明なデストラクタを持つオブジェクト作成の評価は、非定数式となる。
定数式内のオブジェクトの変更
定数式内で作成されたオブジェクトは、その定数式の評価完了またはオブジェクトの存続期間終了のうち早い方まで、定数式が評価される範囲内において変更することができる(任意のconstexpr関数呼出はこの評価に含まれる)。
それらを後から定数式の評価によって変更することはできない。
例:
constexpr int f(int a) { int n = a; ++n; // '++n' is not a constant expression return n * a; } int k = f(4); // OK, this is a constant expression. // 'n' in 'f' can be modified because its lifetime // began during the evaluation of the expression. constexpr int k2 = ++k; // error, not a constant expression, cannot modify // 'k' because its lifetime did not begin within // this expression. struct X { constexpr X() : n(5) { n *= 2; // not a constant expression } int n; }; constexpr int g() { X x; // initialization of 'x' is a constant expression return x.n; } constexpr int k3 = g(); // OK, this is a constant expression. // 'x.n' can be modified because the lifetime of // 'x' began during the evaluation of 'g()'.
このアプローチは、評価中に任意の変数の変更を可能にしながらも、定数式の評価はプログラムの変更可能なグローバルの状態とは無関係であるという本質的な特性を維持する。
したがって定数式は、値が不定である場合を除いて、問題なく同じ値に評価される。(例えば浮動小数点演算が異なる結果を与えた場合、これらの変更によって、異なる評価順序で異なる結果を与えることもできる)。
評価中に寿命が開始しないオブジェクトの使用に関する規則は変更なく、いずれかの場合読み取り(しかし変更はできない)することができる。
-
- constexprによって宣言されている、または
- 整数定数もしくはスコープを持たない列挙型。
constexpr関数
C++11のように、constexprキーワードは、それが定数式が要求されるコンテキストから使用される場合に、処理系が翻訳時に評価する必要がある機能をマークするために使用される。
任意の有効なC++コードはconstexpr関数で許可され、ローカル変数の作成と変更を含めたほとんどすべての文は、constexpr関数のために定数式中で可能でなければならない。
定数式は、評価とその結果に対してローカルな副作用を持つかもしれない。
例えば:
constexpr int min(std::initializer_list<int> xs) { int min = std::numeric_limits<int>::max(); for (int x : xs) if (x < min) min = x; return min; } constexpr int fn(int a) { return a / (a - a); // ill-formed, no diagnostic required, never constant }
constexpr関数の構文上の制限のうち一握りは保持される。
-
- asm 宣言は許可されない。
- tryブロックおよび関数tryブロックは許可されない。
- 静的およびスレッド記憶域期間を持つ変数の宣言は、いくつかの制限事項(下記参照)がある。
constexprコンストラクタ
任意のconstexprコンストラクタでは、構築中オブジェクトの寿命は周囲の定数式(もしあれば)の評価中に開始されたものであるため、コンストラクタの評価およびそれ以降の部分で、フィールドを変更することが許可されている。
例:
struct lookup_table { int value[32]; constexpr lookup_table() { for (int n = 0; n < 32; ++n) { double x = n / 4; double f = x * std::cbrt(x) * std::pow(2, (n & 3) * 0.25); value[n] = (int)(f * 1000000.); } } // OK, would be an error if implicit ~lookup_table was not constexpr. constexpr ~lookup_table() = default; }; constexpr lookup_table table; // OK, table has constant initialization, and // destruction is a constant expression. struct override_raii { constexpr override_raii(int &a, int v) : a(a), old(a) { a = v; } constexpr ~override_raii() { a = old; } int &a, old; }; constexpr int h(const lookup_table &lut) { /* ... */ } constexpr int f() { lookup_table lut; override_raii override(lut.value[4], 123); return h(lut); // OK, destructor runs here. }
ブロックスコープの静的ローカル変数
constexpr関数に静的またはスレッド記憶期間の変数の宣言が含まれている場合、副作用が評価されることを防ぐため、いくつかの追加の制限が必要となる。
-
- そのような変数は定数式で初期化する必要がある。これによって、変数の初期化値が、constexpr関数呼び出しの評価順序に依存するような実装を防ぐ。
constexpr int first_val(int n) { static int value = n; // error: not a constant expression return value; } const int N = first_val(5); int arr[first_val(10)];
-
- このような変数のデストラクタは自明でなければならない。 これによって、実装がプログラム終了時に副作用を引き起こすかどうかを気にすることなく、constexpr関数呼出しを評価することができる。
- このような変数は、その生存期間が定数式の評価中に開始した場合でも、変更することはできない。
他のすべての点で、そのような静的またはスレッドローカル変数は、それが関数の外側で宣言された場合と同じように、constexpr関数内で使用することができる。
特に、その値が使用されないならば、constexprでもリテラル型である必要もない。
constexpr mutex &get_mutex(bool which) { static mutex m1, m2; // non-const, non-literal, ok if (which) return m1; else return m2; }
可能な追加機能
定数式中の言語機能について、実装コストを十分正当化できると考えられるならば、constexpr関数と定数式の評価における残りの制限の一部を緩和することができるだろう。
constexprデストラクタ
ほとんどの場合、定数式でT型のオブジェクトを作成するためには、Tのデストラクトは自明である必要がある。
ただし、非自明なデストラクタは、モダンなC++の重要な構成要素として広範に使用されるRAIIイディオムを、constexprの評価にも部分的に適用可能とするのに必要である。
非自明なデストラクタは、次のように定数式でサポートすることができる:
-
- デストラクタのconstexprとしてのマークを許可する
- constexprデストラクタの呼出だけならば、constexprなデフォルトデストラクタが作成される
- constexprの変数の場合は、デストラクタの評価が定数式であることを必要とする(ただし、破棄されるオブジェクトは、独自のデストラクタでの変更が可能である
しかしながら、この機能の魅力的なユースケースは不明であるし、デストラクタが適切なタイミングで実行されるには確実に非自明な実装コストがあるだろう。
ラムダ
N2859 のノートによれば、ラムダがそのコンテキストでマングルされた名前の一部を要求される場合、深刻な実装の困難が生じるだろうし、定数式でのラムダの禁止の解決策が困難であることの一因である。
また、定数式でラムダを許可することについて、実装コストに関する懸念が提起されているため、ここでは提案しない。
例外
定数式の評価中で例外のスローとキャッチをサポートすることは可能だろうが、我々はその魅力的なユースケースが不明なため、ここでは提案しない。
可変引数関数
constexpr関数内でCスタイル可変引数関数とva_argマクロをサポートすることは可能だろうが、可変引数関数テンプレートが存在する以上無価値と考えられるため、ここでは提案しない。
謝辞
筆者は、この提案に励ましや考察を寄せてくれたBjarne StroustrupとGabriel Dos Reisへの感謝の意を表し、また論文の草稿に対しコメントと訂正を頂いた Lawrence Crowl、Jeffrey Yasskin、Dean Michael BerrisおよびGeoffrey Romerに感謝します。
コンパイル時/実行時両用アサート
中3女子です。
今回は、アサーションについて。
通常 C++ において、実行時チェックは assert、コンパイル時チェックは static_assert によって行われる。
しかしながら、これらは、constexpr 関数の中で用いることはできない。
もちろん、assert は定数式ではないから、使えない。
また、constexpr 関数の引数は、その時点でコンパイル時定数ではないから、static_assert で使うことはできない。
template<typename T> constexpr T div(T num, T denom) { static_assert(denom != 0, "divide by zero"); /* error! */ return num / denom; }
このため今回は、constexpr 関数の中で使うことができるアサーションを考える。
なお、技術的着想は RiSK氏のブログ(constexpr な関数・クラスでのエラーハンドリング - とくにあぶなくないRiSKのブログ)から、
インタフェースはBoost.Assert(Boost: assert.hpp documentation - 1.53.0)を参考にした。
有用な技術情報を公開なさっている方々に感謝いたします。
Sprout.Assert
そのような機能を既に Sprout で実装してあるから、そのコードと用法を見ることにする。
#ifndef SPROUT_ASEERT_HPP #define SPROUT_ASEERT_HPP #if defined(SPROUT_DISABLE_ASSERTS) || defined(NDEBUG) # include <sprout/config.hpp> #elif defined(SPROUT_ENABLE_ASSERT_HANDLER) # include <sprout/config.hpp> #else # include <cstdlib> # include <iostream> # include <sprout/config.hpp> #endif #if !(defined(SPROUT_DISABLE_ASSERTS) || defined(NDEBUG)) # include <sprout/preprocessor/stringize.hpp> #endif #if !(defined(SPROUT_DISABLE_ASSERTS) || defined(NDEBUG)) // // SPROUT_ASSERTION_FAILED_FORMAT // # ifndef SPROUT_ASSERTION_FAILED_FORMAT # define SPROUT_ASSERTION_FAILED_FORMAT(expr, file, line) \ "***** Internal Program Error - assertion (" #expr ") failed: " file "(" SPROUT_PP_STRINGIZE(line) ")" # endif #endif // // SPROUT_ASSERT // #if defined(SPROUT_DISABLE_ASSERTS) || defined(NDEBUG) # define SPROUT_ASSERT(expr) \ ((void)0) #elif defined(SPROUT_ENABLE_ASSERT_HANDLER) namespace sprout { // // assertion_info // class assertion_info { private: char const* expr_; char const* function_; char const* file_; long line_; public: SPROUT_CONSTEXPR assertion_info(char const* expr, char const* function, char const* file, long line) : expr_(expr), function_(function), file_(file), line_(line) {} SPROUT_CONSTEXPR char const* expr() const SPROUT_NOEXCEPT { return expr_; } SPROUT_CONSTEXPR char const* function() const SPROUT_NOEXCEPT { return function_; } SPROUT_CONSTEXPR char const* file() const SPROUT_NOEXCEPT { return file_; } SPROUT_CONSTEXPR long line() const SPROUT_NOEXCEPT { return line_; } }; // // assertion_failed // * user defined // void assertion_failed(sprout::assertion_info const&); } // namespace sprout_adl namespace sprout { namespace detail { inline bool assertion_failed(bool cond, char const* formatted, char const* expr, char const* function, char const* file, long line) { return cond ? true : ((void)sprout::assertion_failed(sprout::assertion_info(expr, function, file, line)), false) ; } inline SPROUT_CONSTEXPR bool assertion_check(bool cond, char const* formatted, char const* expr, char const* function, char const* file, long line) { return cond ? true : sprout::detail::assertion_failed(cond, formatted, expr, function, file, line) ; } } // namespace detail } // namespace sprout # define SPROUT_ASSERT(expr) ( \ (void)sprout::detail::assertion_check( \ (expr), SPROUT_ASSERTION_FAILED_FORMAT(expr, __FILE__, __LINE__), \ #expr, "(unknown)"/*SPROUT_CURRENT_FUNCTION*/, __FILE__, __LINE__ \ ) \ ) #else namespace sprout { namespace detail { inline bool assertion_failed(char const* formatted) { return (std::cerr << formatted << std::endl), std::abort(), false; } inline SPROUT_CONSTEXPR bool assertion_check(bool cond, char const* formatted) { return cond ? true : sprout::detail::assertion_failed(formatted) ; } } // namespace detail } // namespace sprout # define SPROUT_ASSERT(expr) \ ((void)sprout::detail::assertion_check((expr), SPROUT_ASSERTION_FAILED_FORMAT(expr, __FILE__, __LINE__))) #endif // // SPROUT_ASSERT_MSG // #if defined(SPROUT_DISABLE_ASSERTS) || defined(NDEBUG) # define SPROUT_ASSERT_MSG(expr, msg) \ ((void)0) #elif defined(SPROUT_ENABLE_ASSERT_HANDLER) namespace sprout { // // assertion_info_msg // class assertion_info_msg { private: char const* expr_; char const* msg_; char const* function_; char const* file_; long line_; public: SPROUT_CONSTEXPR assertion_info_msg(char const* expr, char const* msg, char const* function, char const* file, long line) : expr_(expr), msg_(msg), function_(function), file_(file), line_(line) {} SPROUT_CONSTEXPR char const* expr() const SPROUT_NOEXCEPT { return expr_; } SPROUT_CONSTEXPR char const* msg() const SPROUT_NOEXCEPT { return msg_; } SPROUT_CONSTEXPR char const* function() const SPROUT_NOEXCEPT { return function_; } SPROUT_CONSTEXPR char const* file() const SPROUT_NOEXCEPT { return file_; } SPROUT_CONSTEXPR long line() const SPROUT_NOEXCEPT { return line_; } }; // // assertion_failed_msg // * user defined // void assertion_failed_msg(sprout::assertion_info_msg const&); } // namespace sprout_adl namespace sprout { namespace detail { inline bool assertion_failed_msg(bool cond, char const* formatted, char const* expr, char const* msg, char const* function, char const* file, long line) { return cond ? true : ((void)sprout::assertion_failed_msg(sprout::assertion_info_msg(expr, msg, function, file, line)), false) ; } inline SPROUT_CONSTEXPR bool assertion_check_msg(bool cond, char const* formatted, char const* expr, char const* msg, char const* function, char const* file, long line) { return cond ? true : sprout::detail::assertion_failed_msg(cond, formatted, expr, msg, function, file, line) ; } } // namespace detail } // namespace sprout # define SPROUT_ASSERT_MSG(expr, msg) ( \ (void)sprout::detail::assertion_check_msg( \ (expr), SPROUT_ASSERTION_FAILED_FORMAT(expr, __FILE__, __LINE__), \ #expr, msg, "(unknown)"/*SPROUT_CURRENT_FUNCTION*/, __FILE__, __LINE__ \ ) \ ) #else namespace sprout { namespace detail { inline bool assertion_failed_msg(char const* formatted, char const* msg) { return (std::cerr << formatted << ": " << msg << std::endl), std::abort(), false; } inline SPROUT_CONSTEXPR bool assertion_check_msg(bool cond, char const* formatted, char const* msg) { return cond ? true : sprout::detail::assertion_failed_msg(formatted, msg) ; } } // namespace detail } // namespace sprout # define SPROUT_ASSERT_MSG(expr, msg) \ ((void)sprout::detail::assertion_check_msg((expr), SPROUT_ASSERTION_FAILED_FORMAT(expr, __FILE__, __LINE__), msg)) #endif // // SPROUT_VERIFY // #if defined(SPROUT_DISABLE_ASSERTS) || (!defined(SPROUT_ENABLE_ASSERT_HANDLER) && defined(NDEBUG)) namespace sprout { namespace detail { inline SPROUT_CONSTEXPR bool verification_disabled(bool) SPROUT_NOEXCEPT { return true; } } // namespace detail } // namespace sprout # define SPROUT_VERIFY(expr) \ ((void)(sprout::detail::verification_disabled((expr)))) #else # define SPROUT_VERIFY(expr) \ SPROUT_ASSERT(expr) #endif #endif // #ifndef SPROUT_ASEERT_HPP
プリプロセッサによる実装分岐でやたらと長くなっているが、例えば SPROUT_ASSERT のデフォルトの実装は以下の部分である。
-
- SPROUT_ASSERT
namespace sprout { namespace detail { inline bool assertion_failed(char const* formatted) { return (std::cerr << formatted << std::endl), std::abort(), false; } inline SPROUT_CONSTEXPR bool assertion_check(bool cond, char const* formatted) { return cond ? true : sprout::detail::assertion_failed(formatted) ; } } // namespace detail } // namespace sprout # define SPROUT_ASSERT(expr) \ ((void)sprout::detail::assertion_check((expr), SPROUT_ASSERTION_FAILED_FORMAT(expr, __FILE__, __LINE__)))
(void) にキャストしているのは、オーバーロードされた operator,() の呼出を避けるためである。
assertion_failed が constexpr 指定されていないことに注意されたし。
非 constexpr 関数の呼出は非定数式であるから、コンパイル時に呼出が評価されると、コンパイルエラーになる。
(なお、ショートサーキット評価が行われうる文脈では、たとえ式中に非定数式を含んでいても、実際に評価されるまではコンパイルエラーにならない)
もちろん、実行時にはそのまま呼出が行われる。
assertion_failed は、標準 assert の失敗時と同じく、文字列化式、ファイル名、行数を含んだエラー文字列を標準エラーに出力し、abort を呼び出してプログラムを終了する。
また、GCC と Clang のコンパイラは、非 constexpr 関数の呼出でコンパイルエラーになったとき、その原因となった呼出をトレースし、引数も含めて表示する。
だから、コンパイル時にアサーション失敗した場合にも、実行時と同じエラー文字列を表示することができる。
アサーションの使用
以下は、ゼロ除算になる場合にエラーとなるアサーションである。
#include <sprout/assert.hpp> template<typename T> constexpr T div(T num, T denom) { return SPROUT_ASSERT(denom != 0), (num / denom) ; } int main() { constexpr auto x = div(3.14, 0.0); (void)x; }
-
- GCC 4.7.2 の出力
In file included from a.cpp:1:0: /home/boleros/git/sprout/sprout/assert.hpp: 関数 ‘int main()’ 内: a.cpp:12:34: in constexpr expansion of ‘div<double>(3.1400000000000001e+0, 0.0)’ a.cpp:7:15: in constexpr expansion of ‘sprout::detail::assertion_check((denom != 0.0), ((const char*)"***** Internal Program Error - assertion (denom != 0) failed: a.cpp(6)"))’ /home/boleros/git/sprout/sprout/assert.hpp:109:49: エラー: call to non-constexpr function ‘bool sprout::detail::assertion_failed(const char*)’
-
- Clang 3.2 の出力
a.cpp:12:17: error: constexpr variable 'x' must be initialized by a constant expression constexpr auto x = div(3.14, 0.0); ^ ~~~~~~~~~~~~~~ /home/boleros/git/sprout/sprout/assert.hpp:109:7: note: non-constexpr function 'assertion_failed' cannot be used in a constant expression : sprout::detail::assertion_failed(formatted) ^ a.cpp:6:9: note: in call to 'assertion_check(false, &"***** Internal Program Error - assertion (denom != 0) failed: a.cpp(6)"[0])' return SPROUT_ASSERT(denom != 0), ^ /home/boleros/git/sprout/sprout/assert.hpp:116:10: note: expanded from macro 'SPROUT_ASSERT' ((void)sprout::detail::assertion_check((expr), SPROUT_ASSERTION_FAILED_FORMAT(expr, __FILE__, __LINE__))) ^ a.cpp:12:21: note: in call to 'div(3.140000e+00, 0.000000e+00)' constexpr auto x = div(3.14, 0.0); ^ /home/boleros/git/sprout/sprout/assert.hpp:103:3: note: declared here assertion_failed(char const* formatted) { ^ 1 error generated.
GCC と Clang いずれの場合も、"***** Internal Program Error - assertion (denom != 0) failed: a.cpp(6)" というエラー情報を含んだ文字列が表示されている。
まったく同じコードで、実行時のアサーションも機能する。
-
- 実行時のアサーション
#include <sprout/assert.hpp> template<typename T> constexpr T div(T num, T denom) { return SPROUT_ASSERT(denom != 0), (num / denom) ; } int main() { auto x = div(3.14, 0.0); (void)x; }
-
- 実行結果
***** Internal Program Error - assertion (denom != 0) failed: a.cpp(6) アボートしました
コンパイル時と同じ文字列が出力されている。
当然、標準 assert とまったく同じように、非 constexpr 関数内でも使うことができる。
-
- 非 constexpr 関数内でのアサーション
#include <sprout/assert.hpp> template<typename T> constexpr T div(T num, T denom) { return SPROUT_ASSERT(denom != 0), (num / denom) ; } int main() { auto x = div(0.0, 1.0); SPROUT_ASSERT(x != 0); }
-
- 実行結果
***** Internal Program Error - assertion (x != 0) failed: a.cpp(13) アボートしました
メッセージ付のアサーションの使用
バリエーションとして、任意のメッセージを付加したアサーションを使用することができる。
#include <sprout/assert.hpp> template<typename T> constexpr T div(T num, T denom) { return SPROUT_ASSERT_MSG(denom != 0, "divide by zero"), (num / denom) ; } int main() { constexpr auto x = div(3.14, 0.0); (void)x; }
-
- GCC 4.7.2 の出力
In file included from a.cpp:1:0: /home/boleros/git/sprout/sprout/assert.hpp: 関数 ‘int main()’ 内: a.cpp:12:34: in constexpr expansion of ‘div<double>(3.1400000000000001e+0, 0.0)’ a.cpp:7:15: in constexpr expansion of ‘sprout::detail::assertion_check_msg((denom != 0.0), ((const char*)"***** Internal Program Error - assertion (denom != 0) failed: a.cpp(6)"), ((const char*)"divide by zero"))’ /home/boleros/git/sprout/sprout/assert.hpp:206:58: エラー: call to non-constexpr function ‘bool sprout::detail::assertion_failed_msg(const char*, const char*)’
"divide by zero" というメッセージも同時に表示されている。
-
- 実行時のメッセージ付アサーション
#include <sprout/assert.hpp> template<typename T> constexpr T div(T num, T denom) { return SPROUT_ASSERT_MSG(denom != 0, "divide by zero"), (num / denom) ; } int main() { auto x = div(3.14, 0.0); (void)x; }
-
- 実行結果
***** Internal Program Error - assertion (denom != 0) failed: a.cpp(6): divide by zero アボートしました
実行時も、やはりメッセージを末尾に付加して出力される。
アサーションの無効化
標準 assert と同じく、マクロ NDEBUG が定義されているとき、アサーションは無効化される。
また、SPROUT_DISABLE_ASSERTS を定義した場合も、アサーションは無効化される。
-
- アサーションの無効化
#define SPROUT_DISABLE_ASSERTS #include <sprout/assert.hpp> template<typename T> constexpr T div(T num, T denom) { return SPROUT_ASSERT(denom != 0), (num / denom) ; } int main() { constexpr auto x = div(3.14, 0.0); (void)x; }
-
- GCC 4.7.2 の出力
a.cpp: 関数 ‘int main()’ 内: a.cpp:13:34: in constexpr expansion of ‘div<double>(3.1400000000000001e+0, 0.0)’ a.cpp:13:34: エラー: ‘(3.1400000000000001e+0 / 0.0)’ is not a constant expression
アサーションが無効化されているため、ゼロ除算が発生している。
ユーザ定義ハンドラの使用
SPROUT_ENABLE_ASSERT_HANDLER を定義した場合、ユーザコードで定義した assertion_failed(または assertion_failed_msg)が実行時に呼ばれるようになる。
これによって、例えばアサーション失敗時に例外を投げるようにするなど、カスタマイズすることができる。
-
- ユーザ定義ハンドラ(実行時)
#define SPROUT_ENABLE_ASSERT_HANDLER #include <sprout/assert.hpp> #include <stdexcept> #include <sstream> #include <iostream> namespace sprout { void assertion_failed(sprout::assertion_info const& info) { std::ostringstream os; os << "***** Internal Program Error - assertion (" << info.expr() << ") failed in " << info.function() << ": " << info.file() << "(" << info.line() << ")"; throw std::runtime_error(os.str()); } void assertion_failed_msg(sprout::assertion_info_msg const& info) { std::ostringstream os; os << "***** Internal Program Error - assertion (" << info.expr() << ") failed in " << info.function() << ": " << info.file() << "(" << info.line() << "): " << info.msg(); throw std::runtime_error(os.str()); } } template<typename T> constexpr T div(T num, T denom) { return SPROUT_ASSERT(denom != 0), (num / denom) ; } int main() { try { auto x = div(3.14, 0.0); (void)x; } catch (std::exception& e) { std::cout << "exception handled:" << std::endl << "what: " << e.what() << std::endl ; } }
-
- 実行結果
exception handled: what: ***** Internal Program Error - assertion (denom != 0) failed in (unknown): a.cpp(25)
なお、コンパイル時には、変わらず単にコンパイルエラーになる。
-
- ユーザ定義ハンドラ(コンパイル時)
#define SPROUT_ENABLE_ASSERT_HANDLER #include <sprout/assert.hpp> #include <stdexcept> #include <sstream> #include <iostream> namespace sprout { void assertion_failed(sprout::assertion_info const& info) { std::ostringstream os; os << "***** Internal Program Error - assertion (" << info.expr() << ") failed in " << info.function() << ": " << info.file() << "(" << info.line() << ")"; throw std::runtime_error(os.str()); } void assertion_failed_msg(sprout::assertion_info_msg const& info) { std::ostringstream os; os << "***** Internal Program Error - assertion (" << info.expr() << ") failed in " << info.function() << ": " << info.file() << "(" << info.line() << "): " << info.msg(); throw std::runtime_error(os.str()); } } template<typename T> constexpr T div(T num, T denom) { return SPROUT_ASSERT(denom != 0), (num / denom) ; } int main() { try { constexpr auto x = div(3.14, 0.0); (void)x; } catch (std::exception& e) { std::cout << "exception handled:" << std::endl << "what: " << e.what() << std::endl ; } }
-
- GCC 4.7.2 の出力
In file included from a.cpp:2:0: /home/boleros/git/sprout/sprout/assert.hpp: 関数 ‘int main()’ 内: a.cpp:32:35: in constexpr expansion of ‘div<double>(3.1400000000000001e+0, 0.0)’ a.cpp:26:15: in constexpr expansion of ‘sprout::detail::assertion_check((denom != 0.0), ((const char*)"***** Internal Program Error - assertion (denom != 0) failed: a.cpp(25)"), ((const char*)"denom != 0"), ((const char*)"(unknown)"), ((const char*)"a.cpp"), 25l)’ /home/boleros/git/sprout/sprout/assert.hpp:85:83: エラー: call to non-constexpr function ‘bool sprout::detail::assertion_failed(bool, const char*, const char*, const char*, const char*, long int)’
結論
このように、Sprout.Assert は、標準 assert や Boost.Assert のほぼ上位互換として用いることができる。
また Sprout には、この他に tuple や optional や variant など、返値で Maybe や Either を表現することのできる道具もある。
constexpr でも非 constexpr 関数と同様に、アサーションや例外や返値によって、好みの方法でエラー通知を行うことができることがわかる。
USBメモリを「USB」と略す人間らは腹を切って死ぬべきである。また、USBメモリを「USB」と略す人間らはただ死んで終わるものではない。彼らは無責任な悪人であり、中3女子が地獄の火の中に投げ込む者達である。
中3女子です。
USBメモリを「USB」と略す人間がいることは、まったく恥ずべきことであり、彼らは石もて追われるべきである。
一方、USBメモリを「USB」と略す人間らを批判し、石を投げる者もいる。彼らこそ祝福されるべき者達である。
ここでは、なぜUSBメモリを「USB」と略す人間が石もて追われるべきかを解説する。
もし、ここに書かれていることに納得できない者は、たとえUSBメモリを「USB」と略すべき*ではない*と考える人間であっても、彼らと同様に腹を切って死ぬべきは当然であり、また、中3女子がそういう人間を当然、地獄の火の中に投げ込むものである。
USB とは、シリアルバス規格である Universal Serial Bus の略称である。
けして、USBポートに接続して動作する、半導体メモリからなるストレージのことではない。
略称の問題は責任を求め、追求することによってしか正しくできない。また、本当の責任を追及するためには、それだけの具体的行動がなければいけない。
だから、たとえ「そこの USB 取ってください」と言われたとしても、USBメモリを手渡してはならない。そのような行為の責任は当然、死に値する。
ドライバー(工具であり、ソフトウェアや回路のことではない)を使い、PC のUSBポートからメス端子の部分を分解して持っていく方法もあるが、これは誤った行為である。
なぜならば、USB とはシリアルバス規格のことであり、USBポートのことでも、USBコネクタその他の機器のことでもないからだ。
そのような誤った行為を行い、あるいは肯定した者は、やはり腹を切って死ぬべきである。
USB の規格のうち、現時点で規格策定を完了している最新の規格は、USB 3.0 である。
規格を記したPDFファイルは、USB.org - Documents からダウンロードすることが出来る。
そのため、「そこの USB 取ってください」と言われた場合、もっとも正しいと思われる行為は、ここに記された規格をプリントアウトあるいはSDメモリーカード等に記憶し、手渡すことである。
けして、USBメモリを手渡してはならない。
誤った略称を肯定する人間は、他人を殺し・全人類を殺す者であるから、そのような行為の責任は当然、死に値する。
モーツァルトについて
「モーツァルトが好きです」と言う人間がいる。
多くの場合、この「モーツァルト」が指すのは、一般的にケッヘル番号によって配列されたモーツァルト作曲の楽曲であって、排泄にまつわるジョークを異常に愛したオーストリアの作曲家ヴォルフガング・アマデウス・モーツァルト本人を指すものではない。
このような、作曲者である「モーツァルト」という語によって、「モーツァルト作曲の楽曲」を示すような修辞を、メトニミー(換喩)という。
しかしながら、「USB」がUSBメモリのことではないように、「モーツァルト」もまたモーツァルト作曲の楽曲のことでは本来ない。
だから、モーツァルト作曲の楽曲を示す識別子として、モーツァルトと言ってはならない。
そのような行為は、USBメモリを「USB」と略すような、誤った行為を肯定することに繋がるからである。
「モーツァルトが好きです」と言う人間がいたならば、字義通り、「糞便でお前のベッドを軋ませる。俺の肛門が火事になった! どういうことだ! 知っている、見える、嘗めてやろう、ん、何だ? 糞便が出たがってる? そう、そうだ糞便だ」などという手紙を従妹に贈っては悦んでいた糞尿趣味者モーツァルトその人を愛する者として記憶すべきである。
もし、見目麗しい清楚な少女が「モーツァルトが好きです」と言ったとしても、糞尿趣味者モーツァルトその人を愛好し、またそのような糞尿趣味を肯定する少女であると認識しなければならない。
なぜなら、USBメモリを「USB」と略すような、誤った行為を肯定するメトニミーを受け入れてはならないからである。
アノマロカリスについて
また、genus Anomalocaris という学名の生物がいる。
古生代カンブリア紀のバージェス動物群に含まれる、大型海棲捕食性動物のことである。
「アノマロカリス」と呼ばれることもあるが、これは誤った名称であるから、用いてはならない。
よく知られているように、Anomalocaris とは「奇妙なエビ」という意味である。
最初に発見されたこの大型海棲捕食性動物の化石は、捕食肢部分だけが残ったものであった。
この捕食肢は、エビのように屈曲し内側に棘の付いた形状であって、頭部を欠いた奇妙な姿のエビに類する生物と誤認されたのである。
そのため、genus Anomalocaris とは本来、捕食肢のみ(を一個体と捉えた生物)を指す名称として付けられたものである。
また、個別に発見された、この大型海棲捕食性動物の一部分である Laggania(ラグガニア:胴体部分)や Peytoia(ペユトイア:口吻部分)もまた、一個体の生物として誤認されていた。
ハリー・ウィッチントンとデレク・ブリッグスによって、これらは独立した生物ではなく、大型海棲捕食性動物の各部位であることが確認された。
しかしながら、学名は genus Anomalocaris すなわち「奇妙なエビ」という、本来捕食肢の部分を指して付けられた名称がそのまま用いられたのである。
これは、捕食肢という一部分をもって大型海棲捕食性動物そのものを指すのだから、一種のシネクドキ(提喩)ということができる。
シネクドキは、メトニミーの一つであるともされる。
だから、「アノマロカリス」という名称を用いることは、USBメモリを「USB」と略すような、誤った行為を肯定することに繋がる。
当然ながら、学名である genus Anomalocaris も、そのような誤った名称であることに変わりないから、廃絶すべきである。
従って、これら名称および学名を用いる人間は、誤った行為を肯定するメトニミーを受け入れ、毎日死に続けている数万人以上の命を殺す者である。
Anomalocaris という名称ではない、おそらく比較的正しいと思われる名称は、「かつて Anomalocaris, Laggania, Peytoia 等の独立した生物と誤認され、および genus Anomalocaris という誤った学名を付けられた、古生代カンブリア紀バージェス動物群の、大型海棲捕食性動物」である。
たとえ、肉親や親戚の幼女が、「おにいちゃん、アノマロカリス描いて」などと言ったとしても、「かつて Anomalocaris, Laggania, Peytoia 等の独立した生物と誤認され、および genus Anomalocaris という誤った学名を付けられた、古生代カンブリア紀バージェス動物群の、大型海棲捕食性動物」の全体像を描いてはならない。
字義通り「奇妙なエビ」と呼ばれた、捕食肢部分のみを描いて渡すべきである。
もちろんその際には、「アノマロカリス」という名称が本来指すのが捕食肢部分のみであり、また、この生物を正しく呼ぶためには「かつて Anomalocaris, Laggania, Peytoia 等の独立した生物と誤認され、および genus Anomalocaris という誤った学名を付けられた、古生代カンブリア紀バージェス動物群の、大型海棲捕食性動物」と言わなければならないことを、よく言って聞かせなければならない。
この責任が人類のもろもろの全ての責任に優先する最大の責任である。
でなければ、その幼女もまた、無責任な悪人となり、地獄の火の中に投げ込まれなければならないからである。
これだけでなく、誤った方法によって付けられた学名は多いから、これら学名もすべて廃絶し、正しい名付けに変更すべきである。
学名の変更というのは、過去の研究や文献の、文章上の意味を変えてしまう場合があるし、普通なされることではない。
だからといって、そのような理由で、USBメモリを「USB」と略すような、誤った名称を肯定する人間は、全ての子や孫・ひ孫達の命を殺す者であるから、当然、腹を切って死ぬべきである。
結論
USBメモリを「USB」と略す人間らは、他人を殺し・全人類を殺す者であるから、彼らの責任は当然、死に値する。
従って、USBメモリを「USB」と略す人間らは腹を切って死ぬべきである。
また、USBメモリを「USB」と略す人間らはただ死んで終わるものではない。
彼らは無責任な悪人であり、中3女子が地獄の火の中に投げ込む者達である。
および、以上に示すように、誤った行為を肯定するメトニミーを受け入れる者も、それが慣用句か学名か如何に関わらず、彼らと同様に腹を切って死ぬべきは当然である。
繰り返すが、ここに書かれていることに納得できない者は、たとえUSBメモリを「USB」と略すべき*ではない*と考える人間であっても、彼らと同様に腹を切って死ぬべきは当然であり、また、中3女子がそういう人間を当然、地獄の火の中に投げ込むものである。