C++ Core Guidlines P.1 change_speed
の例では、次のように使用されるSpeed
タイプを示しています。
change_speed(Speed s); // better: the meaning of s is specified
// ...
change_speed(2.3); // error: no unit
change_speed(23m / 10s); // meters per second
この例の最後の2行に特に興味があります。最初は、change_speed
への引数を持つユニットを提供しない場合、エラーがスローされることを示唆しているようです。最後の行は、一部のm
およびs
リテラルを使用して定義された単位を示しています。これらの新機能はどちらも最新バージョンのC++にありますか?もしそうなら、このようなものがどのように実装され、どのバージョンのC++が必要ですか?
コメントで述べたように、コアガイドラインの例では、ユーザー定義のリテラルを使用して、物理量を直感的に表すアプリケーション固有のタイプを構築しています。特定の例でそれらを説明するために、以下のタイプを検討してください。
/* "Strong" speed type, unit is always [m/s]. */
struct Speed {
long double value;
};
/* "Strong" length type, parameterized by a unit as multiples of [m]. */
template <class Period = std::ratio<1>> struct Length {
unsigned long long value;
};
Length
オブジェクトの単位を追跡することはおそらく意味がありませんが、Speed
インスタンスの場合はそうではありませんが、ここでは最も簡単な例を考えてみましょう。次に、2つのユーザー定義リテラルを見てみましょう。
#include <ratio>
auto operator ""_m(unsigned long long n)
{
return Length<>{n};
}
auto operator ""_km(unsigned long long n)
{
return Length<std::kilo>{n};
}
これらを使用すると、次のようにLength
オブジェクトをインスタンス化できます。
/* We use auto here, because the suffix is so crystal clear: */
const auto lengthInMeter = 23_m;
const auto lengthInKilometer = 23_km;
Speed
インスタンスを解釈するために、Length
をduration
で除算するための適切な演算子を定義しましょう。
#include <chrono>
template <class LengthRatio, class Rep, class DurationRatio>
auto operator / (const Length<LengthRatio>& lhs,
const std::chrono::duration<Rep, DurationRatio>& rhs)
{
const auto lengthFactor = static_cast<double>(LengthRatio::num)/LengthRatio::den;
const auto rhsInSeconds = std::chrono::duration_cast<std::chrono::seconds>(rhs);
return Speed{lengthFactor*lhs.value/rhsInSeconds.count()};
}
ここで、コアガイドラインの例をもう一度見てみましょう。
void change_speed(const Speed& s)
{
/* Complicated stuff... */
}
しかし最も重要なのは、このような関数を呼び出す方法です。
using namespace std::chrono_literals;
int main(int, char **)
{
change_speed(23_m/1s);
change_speed(42_km/3600s);
change_speed(42_km/1h);
return 0;
}
@KillzoneKidがコメントで述べたように、これを機能させるにはC++ 11が必要です。
コードには2つの異なるものが含まれます。
Strong/unit型を使用してコードをより堅牢にする、つまり2つの整数型を区別する。これは一部の言語(Adaなど)に組み込まれていますが、C++には組み込まれていませんが、整数型をラップするクラスを作成して、そのような動作を模倣できます(以下を参照)。
演算子リテラルを使用して、このようなクラスのインスタンスをユーザーフレンドリーな方法で作成します。つまり、1s
ではなくseconds{1}
と記述します。これは単に便利な機能であり、いくつかの場所で役立ちます。
強い整数型を使用すると、コードでエラーが発生しにくくなるため、非常に便利です。*:
seconds
とhours
)内では、浮動小数点型(seconds
/hours
)で表現していない限り、float
をdouble
に変換できません。hours
をseconds
に変換できます。auto speed = 70km / 1h; // Don't bother deducing the type of speed, let the compiler do it for you.
microseconds
を返す場合、これが何であるかを知っていれば、関数をドキュメント化している人が返すことを期待する必要はありませんunsigned long long
は、これがマイクロ秒を表すと述べました...* ここでは暗黙的な変換についてのみ説明します。もちろん、たとえばduration_cast
(精度の低下)を使用して、明示的に変換を行うことができます。
ユニットタイプ
「ユニット」クラスでの整数型のラップは常に利用可能でしたが、C++ 11は1つの標準のラップされた整数型 std::chrono::duration
をもたらしました。
「ユニット」クラスは、次のように定義できます。
int
、double
、...現在、標準ではdurationのようなタイプのみが提供されていますが、次のようなより一般的な基本単位タイプを提供することについての議論(おそらく提案?)があります。
template <class Unit, class Rep, class Ratio = std::ratio<1>> class unit;
...ここでUnit
は、表されるものの種類を示すプレースホルダーです。例:
struct length_t { };
template <class Rep, class Ratio = std::ratio<1>>
using length = unit<length_t, Rep, Ratio>;
しかし、これはまだ標準ではないので、std::chrono::duration
を見てみましょう。
template <class Rep, class Period = std::ratio<1>> class duration;
Rep
テンプレートパラメータはC++タイプです。
double
時間に変換できます。Period
テンプレートパラメータは、duration
タイプと1秒(選択された基本的な継続時間)の間の比率を定義します。
std::ratio
は、2つの整数の比率を表す非常に便利な標準定義型であり、対応する演算(*
、/
、...)を備えています。std::chrono::seconds
、std::chrono::minutes
、...演算子リテラル
これらはC++ 11で導入された リテラル演算子 です。
s
は標準であり、 chrono
標準ライブラリに含まれています。
using namespace std::chrono_literals;
auto one_second = 1s;
auto one_hour = 1h;
m
は標準ではないため、_
のように、(ユーザー定義であるため)23_m
を前に付ける必要があります。次のように独自の演算子を定義できます。
constexpr auto operator "" _m(unsigned long long ull) {
return meters{ull};
}