web-dev-qa-db-ja.com

ユーザー定義リテラルはC ++にどのような新しい機能を追加しますか?

C++ 11 を導入 ユーザー定義リテラル これにより、既存のリテラル(inthexに基づく新しいリテラル構文の導入が可能になります、stringfloat)。これにより、どのタイプでもリテラル表示が可能になります。

例:

// 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として)評価し、後に06_ADのみを評価します(8進数の理由で、0なしで8月と9月を記述する必要はありません)。これは、構文を1974-1/6_ADにすることで回避できます。これにより、演算子の評価順序は機能しますが、不格好です。

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


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

137
Motti

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

#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");
}

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

69
emsr

一見すると、単純な構文糖のようです。

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

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

過去数年間に書いたコードにはほとんど使用されていませんが、C++で使用しなかったからといって、他のC++開発者にとって面白くないというわけではありません。

私たちはC++(およびCでは、私が推測する)、コンパイラ定義のリテラルを使用して、整数を短整数または長整数として入力し、実数をfloatまたはdouble(またはlong double)として入力し、文字列を通常またはワイド文字として入力しました。

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

ユーザータイプを組み込みタイプとして振る舞わせることをまだ1つ忘れていました:ユーザー定義リテラル。

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

ブール値、整数などを含むすべてのプリミティブを構造体にし、すべての構造体をオブジェクトから派生させるという.NETの決定に非常に似ていると思います。この決定だけで、プリミティブを操作するときに.NETがJavaの範囲をはるかに超えてしまいます。これは、ボクシング/アンボックスハッキングJavaが仕様に追加する量に関係ありません。

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

この質問は[〜#〜] you [〜#〜]が回答するためのものです。 Bjarne Stroustrupではありません。ハーブサッターではありません。 C++標準委員会のメンバーではありません。そのため、C++で選択できるのはであり、便利な表記法を組み込み型だけに制限することはありません。

youが必要な場合は、歓迎すべき追加です。 youしない場合は、まあ...使用しないでください。費用は一切かかりません。

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

肥大化???複合体を見せてください!!!

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

ユーザー定義リテラルはC++にどのような新しい機能を追加しますか? で示されているように、複素数を記述できることは、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では、別の型を組み込み型として追加し、組み込み演算子のオーバーロードサポートを追加しました。そして、彼らは別の組み込みリテラル機能を追加しました。

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日付を選択しますが、それはあなたのニーズに依存します。これは、ユーザーが独自のリテラル名を定義できるようにする全体のポイントです。

190
paercebal

数学的なコードにはとてもいいです。私の頭の中では、次の演算子の使用法がわかります。

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

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つはそれほど害はありません。

36

UDLは名前空間になっています(宣言/ディレクティブを使用してインポートできますが、3.14std::iなどのリテラルを明示的に名前空間に入れることはできません)。

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

s for std::stringiのような標準のいくつかの便利なリテラルが(その外観から)表示されないのは残念です。

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

17
coppro

コンテキストを少し追加します。私たちの仕事には、ユーザー定義のリテラルが非常に必要です。 MDE(Model-Driven Engineering)に取り組んでいます。 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ではこれを許可していません。名前を文字ごとに書くことができますが、これは明らかに混乱です。適切なユーザー定義リテラルを使用すると、同様のことを書くことができます。モデル要素名を識別するために「_n」を使用するとします(アイデアを作るためだけに、正確な構文は使用しません)。

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

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

12
Diego Sevilla

Bjarne Stroustrupは、この C++ 11 talk でUDLについて語っています。タイプリッチインターフェースに関する最初のセクションでは、約20分です。

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

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

  2. 豊富に型付けされたコードがキャッチできる型エラーの種類は、実際のコードに影響を与えます。 (彼は重要な定数の寸法誤差のために悪名高い失敗した火星気候オービターの例を挙げています)。

  3. 実際のコードでは、ユニットはめったに使用されません。リッチな型を作成するためにランタイムコンピューティングまたはメモリオーバーヘッドが発生することはコストがかかりすぎるため、人々はそれらを使用しません。 (経験的に、ライブラリは10年前から存在しているにもかかわらず、誰も使用していません)。

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

10
masonk

必要な正当化は、コンパイル時のディメンションチェックのサポートのみです。

auto force = 2_N; 
auto dx = 2_m; 
auto energy = force * dx; 

assert(energy == 4_J); 

たとえば、 PhysUnits-CT-Cpp11 を参照してください。これは、コンパイル時の寸法分析と単位/数量の操作と変換のための小さなC++ 11、C++ 14ヘッダーのみのライブラリです。 Boost.Units よりもシンプル、 nit symbol m、g、sなどのリテラル、 metric prefixes などのm、k、M、標準C++ライブラリ、SIのみ、次元のべき乗にのみ依存します。

8
Martin Moene

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

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

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

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

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

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

ただし、最終的には構文糖衣になりますが、読みやすさにわずかな影響があります。そして、おそらくいくつかの式も書きやすいでしょう(sin(180.0deg)はsin(deg(180.0)よりも書きやすいです)。そして、概念を乱用する人がいるでしょう。しかし、言語を乱用する人はC++のような表現力のあるものではなく、非常に制限の多い言語。

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

6
mstrobl

私はこの機能を一度も必要としたことも望んでいませんでした(しかし、これは Blub 効果かもしれません)。私の膝の不自然な反応は、それが足りないということであり、リモートで追加と解釈される可能性のある操作に対してoperator +をオーバーロードするのがクールだと思う人と同じ人にアピールする可能性があります。

3
fizzer

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

よりカスタマイズされたソースコードを作成する方法は気にしませんが、選択した点は私にとって非常に孤立しているように見えます。

意図された使用でさえ、ソースコードを読むのをはるかに難しくするかもしれません:単一の文字は、文脈から決して特定することができない広大な副作用を持っているかもしれません。 u、l、fに対称性があるため、ほとんどの開発者は1文字を選択します。

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

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

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

2
peterchen

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

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

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

これは_std::string_のラッパーを優先して_std::vector_を捨てたときにも役に立ちました。

2
rr-