ユーザー定義リテラルのすべての「通常の」使用は未定義の動作ですか?


8

ユーザー定義のリテラルアンダースコアで始める必要があります。

これは多かれ少なかれ一般的によく知られているルールであり、ユーザーのリテラルについて話しているすべての素人のサイトで見つけることができます。それは私(そしておそらく他の人たち?)が「なんてでたらめな」ベースで以来、露骨に無視してきたルールでもあります。もちろん、これは厳密には正しくありません。厳密に言うと、これは予約済み識別子を使用するため、未定義の動作を呼び出します(実際には、コンパイラから肩をすくめるほどではありません)。

それで、私が標準のその(私の意見では役に立たない)部分を故意に無視し続けるべきかどうかを考えて、私は実際に何が書かれているかを調べることにしました。なぜなら、誰もが知っていることが重要なのです。重要なのは、標準で書かれていることです。

[over.literal]「一部の」リテラル接尾辞識別子は予約されており、にリンクして[usrlit.suffix]いることを示しています。後者は、アンダースコアで始まるものを除いて、すべて予約されていると述べています。わかりました。これは、私たちがすでに知っていて、明示的に書かれた(または逆に書かれた)こととほぼ同じです。

また、明白だが厄介なことを示唆するメモ[over.literal]含まれています。

上記の制約を除いて、それらは通常の名前空間スコープ関数と関数テンプレートです

まあ、確かにそうです。彼らがそうではないと言っているところはどこにもないので、あなたは彼らがそうであると他に何を期待するでしょう。

しかし、ちょっと待ってください。グローバル名前空間で[lex.name]アンダースコア始まる各識別子は予約されていることを明示的に述べています。

現在、リテラル演算子は通常、明示的に名前空間に入れない限り(これはだれもしないと思います!?)、大部分はグローバル名前空間にあります。したがって、名前はアンダースコアで始まる必要があり、予約されています。特別な例外についての言及はありません。したがって、すべての名前(下線あり、またはなし)は予約済みの名前です。

「通常の」使用法(アンダースコアかどうかにかかわらず)が予約名を使用しているため、ユーザー定義のリテラルを名前空間に入れることが本当に期待されていますか?


1
UDLサフィックスは識別子としてカウントされるのでしょうか。
HolyBlackCat

1
FWIWコードは名前空間に配置する必要があり、それに従えば安全です。
NathanOliver

@ NathanOliver-ReinstateMonica:では、そのリテラルをどのように使用すればよいでしょうか?_km名前空間に何を入れたとしても... (キロメートルの場合)としましょうudl。次に、5 kmのリテラルは次のようになります... 5udl::_km
デイモン

@ NathanOliver-ReinstateMonicaそれは私が思ったことです…しかし、それは真実ではありません、私の答えを見てください。
Konrad Rudolph

1
@Damonそれがusingステートメントの目的です。リテラルを使用する必要があるスコープで、それをインポートするusingステートメントを使用します。
NathanOliver

回答:


6

はい:_グローバル識別子の開始としての使用を禁止することと、非標準のUDLを最初に要求することの組み合わせは、_それらをグローバル名前空間に配置できないことを意味します。しかし、グローバル名前空間を特に UDLなどで汚してはならないので、それほど問題にはなりません。

標準で使用されている従来のイディオムは、UDLをliteralsネームスペースに配置することです(UDLのセットが異なる場合は、それらをinline namespacesそのネームスペースの下に配置します)。そのliterals名前空間は通常、メインの名前空間の下にあります。UDLの特定のセットを使用する場合は、呼び出すusing namespace my_namespace::literalsか、選択したリテラルセットを含むサブ名前空間を呼び出します。

UDLは大幅に省略される傾向があるため、これは重要です。たとえば、標準ではsを使用しますがstd::stringstd::chrono::duration秒単位でも使用します。それらはさまざまな種類のリテラルにs適用されますが(文字列に適用されるのは文字列ですがs、数値に適用されるのは期間です)、省略されたリテラルを使用するコードを読むのは混乱することがあります。したがって、ライブラリのすべてのユーザーにリテラルをスローするべきではありません。彼らはそれらを使用することを選択すべきです。

これら(std::literals::string_literalsおよびstd::literals::chrono_literals)に異なる名前空間を使用することにより、ユーザーは、コードのどの部分にどのリテラルのセットが必要かを事前に知ることができます。


1
残念ながら、この回答は_Fooサフィックスの有効性に対応していないようです。サフィックスは質問から省略されましたが、かなり問題があります。
Konrad Rudolph

3

これは良い質問で、答えはわかりませんが、標準の特定の読みに基づくと、答えは「いいえ、UBではありません」と思います。

[lex.name] /3.2読み取り:

アンダースコアで始まる各識別子は、グローバル名前空間の名前として使用するために実装に予約されています。

明らかに、「グローバル名前空間の名前として」という制限は、実装が名前をどのように使用するかだけでなく、ルール全体に適用されるものとして読む必要があります。つまり、その意味は

「アンダースコアで始まる各識別子は実装に予約されており、実装はそのような識別子をグローバル名前空間の名前として使用できます」

むしろ、

「アンダースコアで始まる識別子をグローバル名前空間の名前として使用することは、実装に予約されています。」

(最初の解釈を信じたとしたらmy_namespace::_foo、たとえば、という関数を誰も宣言できないことを意味します。)

2番目の解釈では、operator""_foo(グローバルスコープ内の)グローバル宣言のようなものは正当です。そのような宣言は_foo名前として使用されないためです。むしろ、識別子は実際の名前の一部にすぎoperator""_fooませ。これは(アンダースコアで始まらない)です。


私は同様の解釈で行くつもりでした。のような関数を定義できると考えるvoid operator+(foo, bar)と、関数名は識別子ではなく名前です。同じことがのために行くoperator "" _foo私たちの場合は名前です。
StoryTeller-Unslander Monica

2

ユーザー定義リテラルのすべての「通常の」使用は未定義の動作ですか?

明らかにそうではありません。

以下は、UDL の慣用的な(つまり、「通常の」)使用法であり、リストしたルールに従って明確に定義されています。

namespace si {
    struct metre {  };

    constexpr metre operator ""_m(long double value) { return metre{value}; }
}

問題のあるケースをリストしましたが、その妥当性についての評価には同意しますが、慣用的なC ++コードでは簡単に回避できるため、偶発的であったとしても、現在の表現の問題は完全にはわかりません。

[over.literal] / 8の例によると、アンダースコアの後に大文字を使用することもできます。

float operator ""E(const char*);    // error: reserved literal suffix (20.5.4.3.5, 5.13.8)
double operator""_Bq(long double);  // OK: does not use the reserved identifier _Bq (5.10)
double operator"" _Bq(long double); // uses the reserved identifier _Bq (5.10)

したがって、唯一の問題は、標準が""とUDL名の間の空白を有意にすることです。


良いキャッチ、大文字のSI単位についてさえも考えていませんでした。
デイモン

1
標準には、それが問題ないことを示す例が含まれていますoperator""_Bq(これoperator""_Kも問題ないことを意味します)。トリックは""、とサフィックスの間のスペースを省略することです。C ++ 17 [over.literal] / 8を参照してください。
Brian

@ブライアン例が規範的な表現の唯一の情報源であるとき、私はそれを嫌います。素晴らしい発見。そして、彼らがこの空白を重要なものにしようと決心したという事実は、さらに混乱しています。いずれにせよ、私は私の答えを修正しました。
Konrad Rudolph

例が唯一の出典であるというわけではありません。むしろ、これはuser-defined-string-literalが単一の前処理トークンであるという事実から来ています。そうは言っても、この例は意図を明確にしているだけでなく、この例がなければこのことに気づかなかったでしょう。
ブライアン

1

はい、独自のユーザー定義リテラルをグローバル名前空間で定義すると、プログラムの形式が正しくありません。

私はこれを自分で実行していません。ルールを守ろうとしているからです。

グローバル名前空間には何も(ABIの安定性のためのmain名前空間やその他のextern "C"ものを含め)置かないでください。

namespace Mine {
  struct meter { double value; };
  inline namespace literals {
    meter operator ""_m( double v ) { return {v}; }
  }
}

int main() {
  using namespace Mine::literals;
  std::cout << 15_m.value << "\n";
}

これ_CAPSは、名前空間であっても、リテラル名として使用できないことも意味します。

呼び出されるインライン名前空間literalsは、ユーザー定義のリテラル演算子をパッケージ化する優れた方法です。それらは、どのリテラルに正確に名前を付ける必要なく、使用したい場所にインポートできます。または、ネームスペース全体をインポートすると、リテラルも取得できます。

これは、stdライブラリがリテラルを処理する方法にも従うので、コードのユーザーには馴染みがあるはずです。


0

接尾辞付きのリテラルを指定する_X、文法は_X「識別子」を呼び出します

そのため、はい。標準により、おそらく誤って、明確に定義されたプログラムで、グローバルスコープでUDT、または大文字で始まるUDTを作成することが不可能になりました。(前者は通常、とにかくやりたいことではないことに注意してください!)

これを編集的に解決することはできません。ユーザー定義のリテラルの名前には、(たとえば)実装が提供する関数の名前との衝突を防ぐ独自の字句「名前空間」が必要です。しかし、私の見解では、これらのルールの結果を指摘し、それらが意図的なものであることを指摘して、どこかに非規範的な注記があると良かったでしょう。


例外があっても、int _x(int);(グローバルで)定義された実装はコンパイルエラーを引き起こしませんか?
Richard Critten

潜在的に興味深い更なる読み物:CWG 1882
オービットでの軽さの

1
@RichardCrittenどういう意味ですか?
オービットのライトネスレース

私は理解しました:「...間違いなく免除が必要...」は、フォームのユーザー定義演算子が_xから免除されるべきであること を示します[lex.name]。私が誤解している場合、次に続くのはごみです。実装がint _x(int);グローバルスコープで関数をすでに宣言している場合(予約済みの名前でlong double operator "" _x(long double);問題ありません)、宣言されたユーザー(たとえば)はコンパイルエラーになります。
Richard Critten

免除でこの問題を解決できる方法はわかりません。
Richard Critten
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.