cpprefjp / site

cpprefjpサイトのMarkdownソース
https://cpprefjp.github.io/
382 stars 157 forks source link

ユーザー定義リテラルの説明について #360

Closed kariya-mitsuru closed 8 years ago

kariya-mitsuru commented 8 years ago

ユーザー定義リテラル の説明で、以下の 2 点について規格での言及箇所が見つからなかったのですが、どちらで言及されてますでしょうか。

ユーザー定義リテラルのサフィックスと組み込みリテラルのサフィックスが一致した場合でも、組み込みリテラルのサフィックスと型が一致しない場合には、ユーザー定義リテラルが使用される。たとえば、浮動小数点数のユーザー定義リテラルとしてLLを定義した場合でも、整数リテラルに対してLLサフィックスを付けた場合には、組み込みのLLサフィックスが使用される。

非グローバル名前空間にリテラル演算子を定義すること

ちなみに、前者は Clang ではできませんでした。 http://melpon.org/wandbox/permlink/bpXMoqJUZdCuQ65i

bolero-MURAKAMI commented 8 years ago

参考までに: ユーザ定義リテラルの“識別子”はアンダースコアから始めなければならず、 アンダースコアから始まる“名前”はグローバル名前空間で予約されているが、 しかしユーザ定義リテラルは“名前でない識別子”なのでグローバル名前空間に定義できる、という議論が以前ありました。 本の虫 http://cpplover.blogspot.jp/2009/09/user-defined-literal.html

faithandbrave commented 8 years ago

まず名前空間の方から。

13.5.8 User-defined literals [over.literal]のパラグラフ2にある以下の文章ですが、

A declaration whose declarator-id is a literal-operator-id shall be a declaration of a namespace-scope function or function template (it could be a friend function (11.3)), an explicit instantiation or specialization of a function template, or a using-declaration (7.3.3). A function declared with a literal-operator-id is a literal operator. A function template declared with a literal-operator-id is a literal operator template.

この一文目を私は、「リテラル演算子IDは、名前空間スコープで宣言しなければならない」と解釈しました。「名前空間スコープ」(3.3.6 Namespace scope [basic.scope.namespace])とは、namespaceキーワードによる名前空間定義の中のことを指します。

無名名前空間でのリテラル演算子の宣言は可能なようなので、「リテラル演算子は名前空間定義のなかで定義すること。これには無名名前空間も含む」と修正すればよいでしょうか。

k-satoda commented 8 years ago

3.3.6 [basic.scope.namespace] p3 より

The outermost declarative region of a translation unit is also a namespace, called the global namespace. A name declared in the global namespace has global namespace scope (also called global scope). ...

namespace 宣言の外も「グローバル名前空間」として「名前空間スコープ」に 含まれます。

faithandbrave commented 8 years ago

ちょっと最近私の負担が大きくて体調がひどいことになっているので、ひとまず数日中に出典だけでも再調査して記載します。書いた直後ならすぐに答えられますが、だいぶ日が経っているので出典を探すだけでもだいぶしんどいです。

faithandbrave commented 8 years ago

組み込み演算子のサフィックスについては、今のところ何を参照して書いたのか思い出せない & 見つけ出せない状態です。少なくてもGoogle検索して書いたわけではなく、提案文書といずれかのバージョンの規格書かドラフト仕様を参照しているので、経緯となる文章のどこかにはあるはずですが、まだ見つかりません。参照した提案文書とissueは解説ページからリンクを貼っていますが、記載漏れはあるかもしれません。 私が解釈違いで書いたものかもしれませんが、近しい表現のものはどこかにあるはずなので、どなたか再調査にご協力いただけると助かります。 Clang 3.9では確かにその仕様のサンプルコードは動きませんでしたが、GCC 6.0では動きました。

それと、できるだけ再調査の負担を減らしたいので、質問の際には質問者の方が何をどう調べた上での質問なのかを記載していただけると助かります。たとえば、「Clangで動かなかった」ということは記載されていましたが、ほかのコンパイラで検証したかどうかが明記されていなかったので、その点についても再調査する必要がありました。

kariya-mitsuru commented 8 years ago

「非グローバル名前空間にリテラル演算子を定義すること」の方は誤りのようなので、私の方で修正します。

あと、前者ですが、今規格書を見ていて思ったのですが、もしかしてこの文面から書かれたものでしょうか。

If a token matches both user-defined-literal and another literal kind, it is treated as the latter. [ Example: 123_km is a user-defined-literal, but 12LL is an integer-literal. —end example ]

であれば、規格書の意図は異なりますので私の方で修正しておきます。

それと、できるだけ再調査の負担を減らしたいので、質問の際には質問者の方が何をどう調べた上での質問なのかを記載していただけると助かります。たとえば、「Clangで動かなかった」ということは記載されていましたが、ほかのコンパイラで検証したかどうかが明記されていなかったので、その点についても再調査する必要がありました。

本質問は、最初に記載した通り「規格での言及箇所が見つからなかった」ために質問しています。 Clang で出来ない云々はあくまで補足情報に過ぎません。 それは、本サイトが規格に関する情報を提供する場所である以上、たとえ全ての実装が同じ挙動を示していたとしても規格と異なる動作であれば手本にすべきではないと考えているからです。 質問に特定の実装での挙動を書くことが不適切であれば、以降の質問では書かないようにします。

faithandbrave commented 8 years ago

それは、本サイトが規格に関する情報を提供する場所である以上、たとえ全ての実装が同じ挙動を示していたとしても規格と異なる動作であれば手本にすべきではないと考えているからです。 質問に特定の実装での挙動を書くことが不適切であれば、以降の質問では書かないようにします。

いえ、「規格での言及箇所が見つからなかった」と「なかった」は違いますので、特定コンパイラでコンパイルが通らないことはコンパイラのバグである可能性があり、特定コンパイラでコンパイルが通ることは記述の正しさを表している可能性があります。そのため、「何を試したか」以外に「何を試さなかったか」が書いてあると助かったということです。

faithandbrave commented 8 years ago

名前空間について、以下のissueで制限の対象がnameからidentifierに変更になっています。

CWG DR 1882. Reserved names without library use

この変更はC++17のドラフト仕様であるN4527に取り込まれているため、C++11段階でグローバル名前空間でリテラル演算子を定義できることは、規格が意図したものではない(つまり仕様バグ)である可能性があります。その場合、少なくてもcpprefjpのドキュメントでは、グローバル名前空間での定義を推奨しないことは記載しておいたほうがいいかもしれません。(仕様バグなら、cpprefjpではバージョンを遡って対応してもよいと思います。言語編とライブラリ編、いくつかのところでそう対応しています。ユーザー定義リテラルの解説ページでもDR1479の対応がそうなっています)

C++14時点の規格でも、サンプルコードで以下の記述があり、元々のnameに対する規約がリテラル演算子の識別子に対しても適用することを意図しているように見えます。

double operator"" _Bq(double); // uses the reserved name _Bq (17.6.4.3.2)

この解釈が合っているかのダブルチェックをどなたかお願いします。

あと、前者ですが、今規格書を見ていて思ったのですが、もしかしてこの文面から書かれたものでしょうか。 If a token matches both user-defined-literal and another literal kind, it is treated as the latter. [ Example: 123_km is a user-defined-literal, but 12LL is an integer-literal. —end example ] であれば、規格書の意図は異なりますので私の方で修正しておきます。

私であれば「"match"を"一致"として記載するだろう」と予測できますが、型に関する言及がないために確信がもてません。もう少し時間をください。いまの記述が間違いであると @kariya-mitsuru さんが確信できる場合は、現時点でその記述を削除していただいてかまいません。これは、嘘の情報をいつまでも公開状態にしておくのはよくないという意図です。その場合は、その後の調査結果を議論して反映する、という2段階の対応をすることになります。

faithandbrave commented 8 years ago

組み込み演算子のサフィックスについて、元にしていた文章は @kariya-mitsuru さんが言及していたところでした。その文章が提案文書の段階では以下のようになっており、

If a token matches both user-defined-literal and another literal kind, then it is treated as the latter. [Example: 123_km, 1.2LL, "Hello"s are all user-defined-literals, but 12LL is an integer-literal. —end example]

1.2LLがユーザー定義リテラルであると書いていたのが出典でした。

kariya-mitsuru commented 8 years ago

なるほど、こんなところにも変更が… でも読んだ限りだとグローバル名前空間で予約されているのはあくまでも name としてだけですね。

Each identifier that contains a double underscore __ or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use. Each identifier that begins with an underscore is reserved to the implementation for use as a name in the global namespace.

そして、私には以下の例の意図が分からないです…

double operator""_Bq(long double); // OK: does not use the reserved identifier _Bq (2.10)
double operator"" _Bq(long double); // uses the reserved identifier _Bq (2.10)

もしかして、文法的に前者は user-defined-string-literal だから良くて、後者は string-literal identifier だからダメってことでしょうか… だとしたら鬼畜すぎる…

でも、その場合はグローバル名前空間でリテラル演算子を定義するのは問題ないことになりますね。 リテラル演算子の nameoperator""接尾辞 になるので。 あくまでも identifier としてダメなのはダブルアンダースコアを含むものと、先頭がアンダースコアに英大文字が続いたものだけなので。

いずれにせよ、説明は難しそうですね…

ちなみに、本質問とは全く関係ないのですが、C++14 からこんな文章も追加されてるのですよね…

A declaration whose literal-operator-id uses such a literal suffix identifier is ill-formed; no diagnostic required.

これが C++11 に遡って適用されるのか否かはまだ確認できていません…

いえ、「規格での言及箇所が見つからなかった」と「なかった」は違いますので、特定コンパイラでコンパイルが通らないことはコンパイラのバグである可能性があり、特定コンパイラでコンパイルが通ることは記述の正しさを表している可能性があります。そのため、「何を試したか」以外に「何を試さなかったか」が書いてあると助かったということです。

わかりました。できる限り「何を試さなかったか」についても書くように心がけます。 が、書き忘れることも良く起きると思いますので、どちらかと言うと気軽に質問返しして頂けるとありがたいです。

faithandbrave commented 8 years ago

もしかして、文法的に前者は user-defined-string-literal だから良くて、後者は string-literal identifier だからダメってことでしょうか… だとしたら鬼畜すぎる…

そのようですね。この例が入ったのは、以下のコミットのようです:

https://github.com/cplusplus/draft/commit/942d83916832804c6f3910441eb65ae169fff20f

しかし、operator""_xがOKでoperator"" _xが規約違反というのは、前者は構文的なハックなので、少なくても推奨しないことは記載したいですね。

8進数リテラルと区切り文字の組み合わせも、構文的なハックでint x = 0'123;のように記述できますが(数値の合間ではなくプレフィックスのあとに区切り文字を書けてしまう)、構文変更の影響が大きかったために禁止されていません。しかし16進数や2進数ではできないことが8進数だけできてしまうので、非推奨にすべきものです。cpprefjpではいまのところ、単にそのことを記述しないようにしています。これは将来的に禁止されそうだからですね。あくまで予測ですが。

k-satoda commented 8 years ago

しかし、operator""_xがOKでoperator"" _xが規約違反というのは、前者は 構文的なハックなので、少なくても推奨しないことは記載したいですね。

(アンダースコア+小文字始まりのサフィックスであればどちらも問題ない ですよね、念のため。 "... for any use" のほうの予約を回避できる件だと して。) これら OK, NG の区別は規格中の例でも明示されているものであり、将来的に 無くなるようなものであればツッコミが入っている可能性が高く、 cpprefjp が 独自に推奨しないとする理由は無いのでは?

予約識別子の使用に該当してしまう operator"" _X は未定義動作になります ので、これについては推奨する・しないという話ではなく、明確にダメとわかる ように書いておくのがいいと思います。

faithandbrave commented 8 years ago

(アンダースコア+小文字始まりのサフィックスであればどちらも問題ない ですよね、念のため。 "... for any use" のほうの予約を回避できる件だと して。)

間違いでした。予約されているのはoperator"" _Xの方でした。

これら OK, NG の区別は規格中の例でも明示されているものであり、将来的に 無くなるようなものであればツッコミが入っている可能性が高く、 cpprefjp が 独自に推奨しないとする理由は無いのでは?

そうですね・・・。例が登場した経緯が、関連するほかのissueのついでに入ったようだったので(Defect Reportも介していない)、正規のプロセスでの合意がとれていないものかと考えていましたが、その後に何度も修正が入っているので、いまの形で合意がとれているのでしょうね。

kariya-mitsuru commented 8 years ago

すみません、C++11 の規格書では

Each name that contains a double underscore or begins with an underscore followed by an uppercase letter (2.12) is reserved to the implementation for any use.

となっているので、文面通りだと operator"" _Bq は(全体で「名前」だから)当てはまらないようにも読めてしまうのですが、これは for any use のところで「名前」としての使用だけじゃないよ、と言ってると考えればいいんですかね? それとも、そもそも Defect だから遡って適用と考えればいいんでしょうか?

faithandbrave commented 8 years ago

ここは、std-discussionのメーリングリストで質問するのがよいのではないでしょうか。確認内容は、以下のような感じでしょうか。

cpprefjpでの過去に遡っての適用ポリシーは、先に述べた通り「"意図しない仕様"の変更」です。DR1882での変更が、元々意図していた仕様への修正であれば、それは破壊的変更ではなく、仕様バグの修正として扱い、過去に遡って適用してよいものだと考えます。

k-satoda commented 8 years ago

"name" に基づく予約の文面は "name" の定義がいい加減だった C++98 当初 からのものなので、識別子との意図的な区別は無かったものと思います。 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#485

それに加えて "for any use" の規定に該当する識別子が処理系独自拡張の ためのキーワード、マクロ名に利用されてきた C を含む長年の実情があった ので、みんな空気を読んで(あるいは無意識のうちに)識別子のことだと解釈 していたということも考えられます。

DR 1882 で現状に沿う明確な文面が得られた今となっては、あんまり深く 突き詰めても実りは無いかと思います。

kariya-mitsuru commented 8 years ago

遅くなりましたが、教えて頂いた内容を基に自分なりに修正してみましたので、こちらの質問はクローズします。 考え違いなどしているかもしれませんので、指摘 or 修正お願いします。