web-dev-qa-db-ja.com

C ++の型の単位

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++が必要ですか?

18
rozzy

コメントで述べたように、コアガイドラインの例では、ユーザー定義のリテラルを使用して、物理量を直感的に表すアプリケーション固有のタイプを構築しています。特定の例でそれらを説明するために、以下のタイプを検討してください。

/* "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インスタンスを解釈するために、Lengthdurationで除算するための適切な演算子を定義しましょう。

#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が必要です。

17
lubgr

コードには2つの異なるものが含まれます。

  1. Strong/unit型を使用してコードをより堅牢にする、つまり2つの整数型を区別する。これは一部の言語(Adaなど)に組み込まれていますが、C++には組み込まれていませんが、整数型をラップするクラスを作成して、そのような動作を模倣できます(以下を参照)。

  2. 演算子リテラルを使用して、このようなクラスのインスタンスをユーザーフレンドリーな方法で作成します。つまり、1sではなくseconds{1}と記述します。これは単に便利な機能であり、いくつかの場所で役立ちます。

強い整数型を使用すると、コードでエラーが発生しにくくなるため、非常に便利です。*

  • 実際のように、期間と長さを表すタイプ間で変換することはできません。
  • 同じ種類のものを表す型(たとえば、secondshours)内では、浮動小数点型(seconds/hours)で表現していない限り、floatdoubleに変換できません。
  • (暗黙的および非暗黙的)変換がスケーリングを処理します。手動で3600を乗算することなく、hourssecondsに変換できます。
  • 変換を処理する現実的な演算子を提供できます。たとえば、この例では、速度を与える長さと期間の間に除算演算子があります。速度の正確なタイプは、長さのタイプと期間のタイプから自動的に推定されます。
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 をもたらしました。

「ユニット」クラスは、次のように定義できます。

  • それが表すもののタイプ:時間、長さ、重量、速度、...
  • これらのデータを表すために使用するC++型:intdouble、...
  • このタイプと同じユニットの「1タイプ」の比率。

現在、標準では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++タイプです。

  • 標準で定義された期間タイプには整数表現があります(実装が定義されています)。
  • 基本となるタイプは、暗黙的に実行できる変換のタイプを定義します。
    • 整数時間を暗黙的に整数秒に変換できます(3600を掛けます)。
    • 精度が低下するため、整数秒を整数時間に暗黙的に変換できます。
    • 整数秒をdouble時間に変換できます。

Periodテンプレートパラメータは、durationタイプと1秒(選択された基本的な継続時間)の間の比率を定義します。

  • std::ratio は、2つの整数の比率を表す非常に便利な標準定義型であり、対応する演算(*/、...)を備えています。
  • 標準は、さまざまな比率で複数の期間タイプを提供します:std::chrono::secondsstd::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};
}
3
Holt