ユーザー定義のリテラル

ユーザー定義のリテラルは、主流のすべてのプログラミング言語に固有の機能です。値と単位を組み合わせる力を与えてくれます。

構文

リテラルは、プログラム内の明示的な値です。これは、true のようなブール値、数値 3 または 4.15 にすることができます。しかし、これは文字 'a' または C 文字列 "hallo" の場合もあります。ラムダ関数でさえ [](int a, int b){ return a+b; } は関数リテラルです。 C++11 では、整数、浮動小数点、文字、および C 文字列の組み込みリテラルにサフィックスを追加することで、ユーザー定義のリテラルを生成できます。

ユーザー定義リテラルは次の構文に従う必要があります:組み込みリテラル + _ + サフィックス。

通常、単位には接尾辞を使用します:

101000101_b
63_s
10345.5_dm
123.45_km
100_m
131094_cm
33_cent
"Hallo"_i18n

しかし、ユーザー定義リテラルの主な利点は何でしょうか? C++ コンパイラは、ユーザー定義のリテラルを対応するリテラル演算子にマップします。このリテラル演算子は、もちろん、プログラマによって実装される必要があります。

魔法

バイナリ値を表すユーザー定義リテラル 0101001000_b を見てみましょう。コンパイラは、ユーザー定義のリテラル 0101001000_b をリテラル演算子 operator"" _b(long long int bin) にマップします。いくつかの特別なルールがまだありません。

  • 引用符 ("") とアンダースコア (_b) の間にはスペースが必要です。
  • 変数 bin にバイナリ値 (0101001000) があります。
  • コンパイラが対応するリテラル演算子を見つけられない場合、コンパイルは失敗します。

C++14 では、ユーザー定義型の代替構文が得られます。スペースを必要としないため、C++11 構文とは異なります。したがって、_C のような予約済みキーワードをサフィックスとして使用し、11_C の形式のユーザー定義リテラルを使用することができます。コンパイラは、11_C をリテラル演算子""_C (unsigned long long int) にマップします。単純なルールは、大文字で始まる接尾辞を使用できるようになりました.

安全性が重要なソフトウェアを作成する場合、ユーザー定義リテラルは最新の C++ のキラー機能です。なんで?ユーザー定義リテラルのリテ​​ラル演算子への自動マッピングのおかげで、タイプ セーフな算術演算を実装できます。コンパイラは、リンゴとナシを追加しないように注意します。例?

週に平均何メートル運転しますか?この質問は長い間私を悩ませてきました.

距離によるタイプセーフな計算

詳細を説明する前に、メイン プログラムを示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// average.cpp

#include <distance.h>
#include <unit.h>

using namespace Distance::Unit;

int main(){

 std:: cout << std::endl;

 std::cout << "1.0_km: " << 1.0_km << std::endl;
 std::cout << "1.0_m: " << 1.0_m << std::endl;
 std::cout << "1.0_dm: " << 1.0_dm << std::endl;
 std::cout << "1.0_cm: " << 1.0_cm << std::endl;
 
 std::cout << std::endl;

 std::cout << "0.001 * 1.0_km: " << 0.001 * 1.0_km << std::endl;
 std::cout << "10 * 1_dm: " << 10 * 1.0_dm << std::endl;
 std::cout << "100 * 1.0cm: " << 100 * 1.0_cm << std::endl;
 std::cout << "1_km / 1000: " << 1.0_km / 1000 << std::endl;

 std::cout << std::endl;
 std::cout << "1.0_km + 2.0_dm + 3.0_dm + 4.0_cm: " << 1.0_km + 2.0_dm + 3.0_dm + 4.0_cm << std::endl;
 std::cout << std::endl;
 
 auto work= 63.0_km;
 auto workPerDay= 2 * work;
 auto abbrevationToWork= 5400.0_m;
 auto workout= 2 * 1600.0_m;
 auto shopping= 2 * 1200.0_m;
 
 auto distPerWeek1= 4*workPerDay-3*abbrevationToWork+ workout+ shopping;
 auto distPerWeek2= 4*workPerDay-3*abbrevationToWork+ 2*workout;
 auto distPerWeek3= 4*workout + 2*shopping;
 auto distPerWeek4= 5*workout + shopping;

 std::cout << "distPerWeek1: " << distPerWeek1 << std::endl;
 
 auto averageDistance= getAverageDistance({distPerWeek1,distPerWeek2,distPerWeek3,distPerWeek4});
 std::cout<< "averageDistance: " << averageDistance << std::endl;
 
 std::cout << std::endl;

}

リテラル演算子は、名前空間 Distance::unit に実装されています。名前の競合は 2 つの理由で発生する可能性が非常に高いため、ユーザー定義のリテラルには名前空間を使用する必要があります。まず、サフィックスは通常非常に短いです。第二に、接尾辞は通常、すでに略語が確立されている単位を表します。プログラムでは接尾辞 km、m、dm、cm を使用しました。

これがプログラムの出力です。私の距離の単位はメートルです。

12 ~ 15 行目にさまざまな距離を表示します。 19 行目から 22 行目で、さまざまな解像度でメートルを計算します。最後のテストは非常に有望に見えます。
1.0_km + 2.0_dm + 3.0_dm + 4.0_cm は 1000.54 m (54 行目) です。コンパイラは、すべての単位で計算を処理します。

重要な質問が残っています。週に平均何メートル運転しますか?便宜上、work、workPerDay、abbrevationToWork、および shopping という定数をいくつか定義します。これらは 4 週間の構成要素です (34 ~ 37 行目)。私は車で最初の週に 493 km 行きました。関数 getAverageDittance (41 行目) は、平均を取得するのに役立ちます。イニシャライザリストを使用して呼び出す必要があります。私は週平均 255900 m を運転します。それを変える必要があります!そして、それは変わりました。私は今、独立したトレーナーです。

ボンネットの下

私は一つの事実を無視しました。 MyDistance オブジェクトはどこで定義されていますか?それらは、自動型推定の背後にあるプログラムに隠されています。したがって、変数 work の明示的な型 (28 行目) は Distance::Distance です。行 28 は、Distance::MyDistance work=63.0_km;

と同等です。

ソース コードで 1.5_km + 105.1_m を使用すると、次の手順が自動的に実行されます。コンパイラは、最初に接尾辞 km および m を対応するリテラル演算子にマップします。次に、コンパイラは + 演算子を MyDistance オブジェクトのオーバーロードされた + 演算子にマップします。どちらの手順も、プログラマが契約の一部として適切な演算子を実装する場合にのみ機能します。これは、この具体的なケースでは、リテラル演算子と + 演算子を実装する必要があることを意味します。グラフィック内の黒い矢印は、コンパイラによって自動的に実行されるマッピングを表しています。赤い矢印は、プログラマーが実装する必要がある機能を表しています。

グラフィックを完成させるためにまだ足りないもの。右!赤い矢印の後ろの肉

プログラマーのタスク

最初に、既知の演算子のオーバーロードについて説明します。クラス MyDistance の基本演算 (15 ~ 28 行目) と出力演算子 (30 ~ 33 行目) をオーバーロードしました。演算子はグローバル関数であり、友情のおかげでクラスの内部を使用できます。プライベート変数 m に距離を格納します。関数 getAverageDistance (行 41 ~ 45) は、オーバーロードされた加算および除算演算子を適用しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// distance.h

#ifndef DISTANCE_H
#define DISTANCE_H

#include <iostream>
#include <ostream>


namespace Distance{
 class MyDistance{
 public:
 MyDistance(double i):m(i){}

 friend MyDistance operator+(const MyDistance& a, const MyDistance& b){
 return MyDistance(a.m + b.m);
 }
 friend MyDistance operator-(const MyDistance& a,const MyDistance& b){
 return MyDistance(a.m - b.m);
 }
 
friend MyDistance operator*(double m, const MyDistance& a){ return MyDistance(m*a.m); } friend MyDistance operator/(const MyDistance& a, int n){ return MyDistance(a.m/n); } friend std::ostream& operator<< (std::ostream &out, const MyDistance& myDist){ out << myDist.m << " m"; return out; } private: double m; }; } Distance::MyDistance getAverageDistance(std::initializer_list<Distance::MyDistance> inList){ auto sum= Distance::MyDistance{0.0}; for (auto i: inList) sum = sum + i ; return sum/inList.size(); } #endif

より短いですが、よりスリリングなのはリテラル演算子です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// unit.h

#ifndef UNIT_H
#define UNIT_H

#include <distance.h>

namespace Distance{

 namespace Unit{
 MyDistance operator "" _km(long double d){
 return MyDistance(1000*d);
 }
 MyDistance operator "" _m(long double m){
 return MyDistance(m);
 }
 MyDistance operator "" _dm(long double d){
 return MyDistance(d/10);
 }
 MyDistance operator "" _cm(long double c){
 return MyDistance(c/100);
 }
 }
}

#endif

リテラル演算子は引数として long double を取り、MyDistance オブジェクトを返します。 MyDistance は自動的にメートルに正規化されます。そしていま?それが、プログラマーが提供しなければならない機能のすべてです。

私は自分のプログラムにある 1 つの大きな最適化の可能性を完全に無視しました。ほとんどすべての操作はコンパイル時に実行できます。ほとんどすべてのオブジェクトは、コンパイル時にインスタンス化できます。これを実現するには、操作とオブジェクトをそれぞれ constexpr として宣言する必要があります。ポスト定数式でこの機能を紹介します。

次は?

浮動小数点数だけでなく、ユーザー定義リテラルを定義できます。整数、文字、および C 文字列に対して実行できます。さらに、C++ には、整数と浮動小数点数に対して 2 つの方法があります。一方は調理済み、もう一方は生と呼ばれます。ユーザー定義リテラルについては、まだまだ書きたいことがたくさんあります。次の投稿をお待ちください。