144

C++11では、既存のリテラル ( 、、、 )に基づいて新しいリテラル構文を導入できるユーザー定義リテラルが導入されているため、どの型でもリテラル表現を使用できます。inthexstringfloat

例:

// imaginary numbers
std::complex<long double> operator "" _i(long double d) // cooked form
{ 
    return std::complex<long double>(0, d); 
}
auto val = 3.14_i; // val = complex<long double>(0, 3.14)

// binary values
int operator "" _B(const char*); // raw form
int answer = 101010_B; // answer = 42

// std::string
std::string operator "" _s(const char* str, size_t /*length*/) 
{ 
    return std::string(str); 
}

auto hi = "hello"_s + " world"; // + works, "hello"_s is a string not a pointer

// units
assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

一見これは非常にクールに見えますが、実際にどのように適用できるのか疑問に思っていました.接尾辞を付けて日付を作成しようとしたとき、_AD演算子_BCの順序のために問題があることがわかりました. 1974/01/06_AD最初1974/01に (プレーンなints として) を評価し、後で評価します ( 8 進数の理由06_ADで、8 月と 9 月を なしで書かなければならないことは言うまでもありません)。これは、演算子の評価順序が機能するよう0に構文を be にすることで回避できますが、扱いにくいです。1974-1/6_AD

要するに、私の質問はこれです。この機能はそれ自体を正当化すると思いますか? C++ コードを読みやすくするために、他にどのようなリテラルを定義しますか?


2011 年 6 月の最終ドラフトに合わせて構文を更新

4

12 に答える 12

195

一見すると、単純なシンタックス シュガーのように見えます。

しかし、より深く見てみると、C++ ユーザーのオプションを拡張して、別個の組み込み型とまったく同じように動作するユーザー定義型を作成するため、これは構文糖衣以上のものであることがわかります。この小さな「おまけ」は、C++ への非常に興味深い C++11 の追加です。

本当に C++ で必要ですか?

過去数年間に書いたコードにはほとんど使用されていませんが、C++ で使用しなかったからといって、別の C++ 開発者にとって興味深いものではないということにはなりません。

C++ (およびおそらく C) では、コンパイラ定義のリテラルを使用して、整数を short または long 整数として、実数を float または double (または long double) として、文字列を normal または wide char として型付けしていました。 .

C++ では、潜在的にオーバーヘッド (インライン化など) なしで、独自の型 (つまり、クラス) を作成する可能性がありました。型に演算子を追加して、同様の組み込み型のように動作させることができました。これにより、C++ 開発者は、行列と複素数が言語自体に追加されている場合と同じように自然に使用できるようになります。キャスト演算子を追加することもできます (これは通常は悪い考えですが、場合によっては正しい解決策です)。

ユーザー型を組み込み型として動作させるために、まだ 1 つのことを見逃していました。それは、ユーザー定義のリテラルです。

したがって、これは言語の自然な進化だと思いますが、可能な限り完全にするために、「型を作成し、それを組み込み型として可能な限り動作させたい場合は、ここにツールがあります。 ..

ブール値、整数などを含むすべてのプリミティブを構造体にし、すべての構造体をオブジェクトから派生させるという.NETの決定と非常に似ていると思います。この決定だけでも、プリミティブを扱う場合、.NET は Java の範囲をはるかに超えたものになります。Java がその仕様にどれだけボックス化/ボックス化解除のハックを追加しようとも。

あなたは本当にC ++でそれを必要としますか?

この質問はあなたが答えるものです。Bjarne Stroustrup ではありません。ハーブサッターではありません。C++ 標準委員会のメンバーではありません。これが、 C++ で選択できる理由であり、組み込み型だけに有用な表記法を制限しません。

あなたがそれ必要とするなら、それは歓迎すべき追加です。そうでない場合は、使用しないでください。それはあなたに何もかかりません。

機能がオプションである言語、C++ へようこそ。

むくんだ???コンプレックス見せて!!!

肥大化したものと複雑なものには違いがあります (しゃれが意図されています)。

ユーザー定義リテラルが C++ に追加する新しい機能についてNiels が示したように? 、複素数を記述できることは、C および C++ に「最近」追加された 2 つの機能の 1 つです。

// C89:
MyComplex z1 = { 1, 2 } ;

// C99: You'll note I is a macro, which can lead
// to very interesting situations...
double complex z1 = 1 + 2*I;

// C++:
std::complex<double> z1(1, 2) ;

// C++11: You'll note that "i" won't ever bother
// you elsewhere
std::complex<double> z1 = 1 + 2_i ;

現在、C99 の「double complex」型と C++ の「std::complex」型の両方で、演算子のオーバーロードを使用して、乗算、加算、減算などを行うことができます。

しかし、C99 では、別の型が組み込み型として追加され、組み込み演算子のオーバーロード サポートが追加されました。そして、組み込みのリテラル機能がもう 1 つ追加されました。

C++ では、言語の既存の機能を使用しただけで、文字どおりの機能が言語の自然な進化であると判断し、それを追加しました。

C では、別のタイプに同じ表記の拡張が必要な​​場合、量子波関数 (または 3D ポイント、または作業分野で使用している基本的なタイプ) を組み込み型としての C 標準は成功します。

C++11 では、自分で実行できます。

Point p = 25_x + 13_y + 3_z ; // 3D point

肥大化していますか?いいえ、C と C++ の両方のコンプレックスがリテラルのコンプレックス値を表現する方法を必要としていることが示すように、その必要性はあります。

設計が間違っていませんか?いいえ、拡張性を考慮して、他のすべての C++ 機能と同様に設計されています。

表記のみですか?コードにタイプ セーフを追加することもできるため、いいえ。

たとえば、CSS 指向のコードを想像してみましょう。

css::Font::Size p0 = 12_pt ;       // Ok
css::Font::Size p1 = 50_percent ;  // Ok
css::Font::Size p2 = 15_px ;       // Ok
css::Font::Size p3 = 10_em ;       // Ok
css::Font::Size p4 = 15 ;         // ERROR : Won't compile !

これにより、値の代入に強力な型付けを適用することが非常に簡単になります。

は危険ですか?

良い質問。これらの関数に名前空間を付けることはできますか? もしそうなら、ジャックポット!

何事もそうですが、道具の使い方を誤ると自殺する可能性があります。Cは強力で、Cガンを誤用すると頭を撃ち落とすことができます. C++ には C 銃がありますが、メス、テーザー、およびツールキットにあるその他のツールも含まれています。メスを誤用すると、出血して死ぬ可能性があります。または、非常にエレガントで堅牢なコードを構築できます。

すべての C++ 機能と同様に、本当に必要ですか? これは、C++ で使用する前に答えなければならない質問です。そうしないと、費用はかかりません。しかし、本当にそれが必要な場合、少なくとも言語はあなたを失望させません.

日付の例?

あなたのエラーは、私には思えますが、演算子を混在させていることです:

1974/01/06AD
    ^  ^  ^

/ は演算子であるため、コンパイラはそれを解釈する必要があるため、これを回避することはできません。そして、私の知る限り、それは良いことです。

あなたの問題の解決策を見つけるために、リテラルを別の方法で書きます。例えば:

"1974-01-06"_AD ;   // ISO-like notation
"06/01/1974"_AD ;   // french-date-like notation
"jan 06 1974"_AD ;  // US-date-like notation
19740106_AD ;       // integer-date-like notation

個人的には、整数と ISO の日付を選択しますが、それはあなたのニーズによって異なります。これが、ユーザーが独自のリテラル名を定義できるようにすることの要点です。

于 2008-10-26T14:17:30.703 に答える
72

コンストラクター呼び出しの代わりにユーザー定義リテラルを使用することに利点がある場合を次に示します。

#include <bitset>
#include <iostream>

template<char... Bits>
  struct checkbits
  {
    static const bool valid = false;
  };

template<char High, char... Bits>
  struct checkbits<High, Bits...>
  {
    static const bool valid = (High == '0' || High == '1')
                   && checkbits<Bits...>::valid;
  };

template<char High>
  struct checkbits<High>
  {
    static const bool valid = (High == '0' || High == '1');
  };

template<char... Bits>
  inline constexpr std::bitset<sizeof...(Bits)>
  operator"" _bits() noexcept
  {
    static_assert(checkbits<Bits...>::valid, "invalid digit in binary string");
    return std::bitset<sizeof...(Bits)>((char []){Bits..., '\0'});
  }

int
main()
{
  auto bits = 0101010101010101010101010101010101010101010101010101010101010101_bits;
  std::cout << bits << std::endl;
  std::cout << "size = " << bits.size() << std::endl;
  std::cout << "count = " << bits.count() << std::endl;
  std::cout << "value = " << bits.to_ullong() << std::endl;

  //  This triggers the static_assert at compile time.
  auto badbits = 2101010101010101010101010101010101010101010101010101010101010101_bits;

  //  This throws at run time.
  std::bitset<64> badbits2("2101010101010101010101010101010101010101010101010101010101010101_bits");
}

利点は、実行時例外がコンパイル時エラーに変換されることです。文字列を取得するビットセットctorに静的アサートを追加することはできませんでした(少なくとも文字列テンプレート引数がない場合はそうではありません)。

于 2011-10-26T17:41:55.843 に答える
37

数学コードにはとてもいいです。私の心の中では、次の演算子の使用法を見ることができます:

度の度。これにより、絶対角度をより直感的に書くことができます。

double operator ""_deg(long double d)
{ 
    // returns radians
    return d*M_PI/180; 
}

また、さまざまな固定小数点表現 (DSP およびグラフィックスの分野でまだ使用されている) にも使用できます。

int operator ""_fix(long double d)
{ 
    // returns d as a 1.15.16 fixed point number
    return (int)(d*65536.0f); 
}

これらは、それを使用する方法の良い例のように見えます。コード内の定数を読みやすくするのに役立ちます。これもコードを読めなくする別のツールですが、すでに多くのツールが乱用されているため、もう 1 つ追加してもそれほど害はありません。

于 2008-10-26T10:16:31.733 に答える
17

UDL はネームスペース化されています (宣言/ディレクティブを使用してインポートできますが、 のようなリテラルを明示的にネームスペース化することはできません3.14std::i)。

それらが実際にテンプレート化 (および constexpr 化) できるという事実は、UDL を使用して非常に強力な機能を実行できることを意味します。Bigint の作成者は、(constexpr またはテンプレートを介して) コンパイル時に計算された任意の大きな定数を最終的に持つことができるので、本当に満足しています。

虚数単位のsforstd::stringやfor のように、(見た目からして) 標準にいくつかの有用なリテラルが表示されないのは残念です。i

UDL によって節約されるコーディング時間は実際にはそれほど多くはありませんが、可読性が大幅に向上し、より多くの計算をコンパイル時にシフトして実行を高速化できます。

于 2008-10-26T18:20:03.473 に答える
13

Bjarne Stroustrup は、このC++11 トークの UDL について話します。型が豊富なインターフェイスに関する最初のセクションでは、約 20 分です。

UDL に関する彼の基本的な議論は、次のような三段論法の形をとっています。

  1. 「自明な」型、つまり組み込みプリミティブ型は、自明な型エラーのみをキャッチできます。より豊富な型を持つインターフェイスにより、型システムはより多くの種類のエラーをキャッチできます。

  2. 豊富に型指定されたコードがキャッチできる型エラーの種類は、実際のコードに影響を与えます。(彼は、重要な定数の寸法エラーが原因で悪名高く失敗したマーズ クライメート オービターの例を挙げています)。

  3. 実際のコードでは、単位はめったに使用されません。豊富な型を作成するために実行時の計算やメモリのオーバーヘッドが発生するとコストがかかりすぎるため、人々はそれらを使用しません。(経験的に、ライブラリは 10 年前から使用されていますが、誰も使用していません)。

  4. したがって、エンジニアが実際のコードでユニットを使用できるようにするためには、(1) 実行時のオーバーヘッドが発生せず、(2) 表記上許容できるデバイスが必要でした。

于 2013-09-09T00:10:00.650 に答える
12

少しコンテキストを追加さ​​せてください。私たちの仕事では、ユーザー定義のリテラルが非常に必要です。MDE(モデル駆動型エンジニアリング)に取り組んでいます。モデルとメタモデルを C++ で定義したいと考えています。Ecore から C++ へのマッピング ( EMF4CPP ) を実際に実装しました。

問題は、C++ でモデル要素をクラスとして定義できる場合に発生します。メタモデル(Ecore)を引数付きのテンプレートに変換するアプローチをとっています。テンプレートの引数は、型とクラスの構造上の特徴です。たとえば、2 つの int 属性を持つクラスは次のようになります。

typedef ::ecore::Class< Attribute<int>, Attribute<int> > MyClass;

ただし、モデルまたはメタモデルのすべての要素には、通常、名前があることがわかります。私たちは書きたいと思います:

typedef ::ecore::Class< "MyClass", Attribute< "x", int>, Attribute<"y", int> > MyClass;

ただし、文字列はテンプレートの引数として禁止されているため、C++、C++0x ではこれが許可されていません。char という名前を char ごとに書くことができますが、これは明らかに混乱しています。適切なユーザー定義のリテラルを使用すると、同様のものを書くことができます。モデル要素名を識別するために「_n」を使用するとします (正確な構文は使用しません。アイデアを作成するためだけです)。

typedef ::ecore::Class< MyClass_n, Attribute< x_n, int>, Attribute<y_n, int> > MyClass;

最後に、これらの定義をテンプレートとして持つことは、モデル要素をトラバースするアルゴリズム、モデル変換などを非常に効率的に設計するのに大いに役立ちます。これは、型情報、識別、変換などはコンパイル時にコンパイラによって決定されるためです。

于 2010-10-29T11:19:42.753 に答える
6

うーん... この機能についてはまだ考えていません。あなたのサンプルはよく考えられており、確かに興味深いものです。C++ は現在でも非常に強力ですが、残念ながら、コードの断片で使用されている構文は非常に複雑な場合があります。読みやすさは、すべてではないにしても、少なくとも十分です。そして、そのような機能は、より読みやすくするために調整されます。私があなたの最後の例を取るなら

assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

...今日はそれをどのように表現するのだろうか。KG クラスと LB クラスがあり、暗黙的なオブジェクトを比較します。

assert(KG(1.0f) == LB(2.2f));

そして、それも同様です。より長い名前を持つ型や、アダプターを作成しないためのこのような優れたコンストラクターを持つことが期待できない型では、オンザフライの暗黙的なオブジェクトの作成と初期化のための優れた追加になる可能性があります。一方、メソッドを使用してオブジェクトを作成および初期化することもできます。

しかし、数学に関してはニルスに同意します。たとえば、C および C++ の三角関数では、ラジアンでの入力が必要です。私は度数で考えているので、Nils が投稿したような非常に短い暗黙的な変換は非常に優れています。

最終的にはシンタックス シュガーになりますが、読みやすさにわずかな影響があります。また、いくつかの式も書きやすいでしょう (sin(180.0deg) は sin(deg(180.0)) よりも書きやすいです)。そして、この概念を悪用する人も出てきます。しかし、言語を乱用する人は、 C++ のような表現力のある言語ではなく、非常に制限的な言語です。

ああ、私の投稿は基本的に何も言っていない: 大丈夫だろう、影響はそれほど大きくないだろう. 心配しないでください。:-)

于 2008-10-26T10:31:52.857 に答える
3

私はこの機能を必要としたり、望んだりしたことはありません (ただし、これはBlub効果の可能性があります)。私のひざまずく反応は、それは不自由であり、リモートで追加として解釈される可能性のある操作に対して operator+ をオーバーロードするのはクールだと考える同じ人々にアピールする可能性が高いということです.

于 2008-10-26T10:03:09.483 に答える
2

C++ は通常、使用される構文について非常に厳密です。プリプロセッサを除いて、カスタム構文/文法を定義するために使用できるものはあまりありません。たとえば、既存のオペラトをオーバーロードすることはできますが、新しいものを定義することはできません - IMO これは C++ の精神と非常に調和しています。

よりカスタマイズされたソース コードのいくつかの方法は気にしません。

意図的に使用したとしても、ソース コードを読むのがはるかに難しくなる可能性があります。単一の文字には、コンテキストからは決して識別できない広範囲にわたる副作用がある可能性があります。u、l、f が対称であるため、ほとんどの開発者は 1 文字を選択します。

これはスコーピングも問題に変える可能性があり、グローバル名前空間で単一文字を使用することはおそらく悪い習慣と見なされ、ライブラリを簡単に混合できると想定されているツール (名前空間と記述識別子) はおそらくその目的を無効にします。

「auto」との組み合わせ、またはboost unitsのようなユニット ライブラリとの組み合わせにもメリットがあると思いますが、この追加に値するほどではありません。

しかし、私たちはどんな賢いアイデアを思いつくのだろうか。

于 2008-10-26T19:44:30.433 に答える
2

次のようなバイナリ文字列にユーザー リテラルを使用しました。

 "asd\0\0\0\1"_b

std::string(str, n)コンストラクターを使用し\0て、文字列を半分にカットしないようにします。(プロジェクトは、さまざまなファイル形式で多くの作業を行います。)

std::stringこれは、 のラッパーを支持して捨てたときにも役に立ちましたstd::vector

于 2015-08-27T13:24:22.490 に答える
-5

その事のライン ノイズは巨大です。読むのも恐ろしい。

私に知らせてください、彼らは新しい構文の追加を何らかの例で推論しましたか? たとえば、すでに C++0x を使用しているプログラムがいくつかありますか?

私にとって、この部分:

auto val = 3.14_i

この部分を正当化しない:

std::complex<double> operator ""_i(long double d) // cooked form
{ 
    return std::complex(0, d);
}

他の 1000 行でも i-syntax を使用したとしても、そうではありません。あなたが書くなら、おそらくそれに沿って何か他のことを10000行も書くでしょう。特に、おそらくどこにでもこれを書く可能性がある場合:

std::complex<double> val = 3.14i

'auto' -キーワードは正当化される可能性がありますが、おそらくのみです。ただし、この点では C++0x よりも優れているため、C++ だけを取り上げます。

std::complex<double> val = std::complex(0, 3.14);

それは...とても簡単です。どこでも使用する場合、すべての標準および先のとがった括弧は単に不自由だと思っていました。std::complex を complex に変換するために、C++0x にどのような構文があるかは推測しません。

complex = std::complex<double>;

それはおそらく簡単なことですが、C++0x ではそれほど単純ではないと思います。

typedef std::complex<double> complex;

complex val = std::complex(0, 3.14);

多分?>:)

とにかく、要点は: std::complex(0, 3.14); の代わりに 3.14i を書くことです。いくつかの非常に特殊なケースを除いて、全体として多くの時間を節約することはできません。

于 2008-10-26T11:14:53.317 に答える