スポンサーリンク

[雑談] C++のユーザー定義リテラルについて考えた話

調べていて思ったこと。

ユーザー定義リテラルは、リテラルの末尾につけるサフィックスを自分で定義する機能。「1.0f」のfの部分を自分で定義できる。

あくまでリテラルにつけるものなので、変数等には使えない。

operator"" の後にサフィックスを与える。 「_」開始でなくてもビルドは通るが、必ず先頭に「_」をつけなければいけないらしい。

以下のような定義例がしばしばみられる。

#include <iostream>

// 名前空間を定義することで、これが有効な範囲でだけ使用できる
namespace suffix {

  long double operator""  _k(const long double value) { return value * pow(10, 3); }
  long double operator""  _h(const long double value) { return value * pow(10, 2); }
  long double operator"" _da(const long double value) { return value * pow(10, 1); }
  /* 10^0 に接頭語はない */
  long double operator""  _d(const long double value) { return value / pow(10, 1); }
  long double operator""  _c(const long double value) { return value / pow(10, 2); }
  long double operator""  _m(const long double value) { return value / pow(10, 3); }
}


int main()
{
  using namespace suffix;

  //  1.0 × 10^3  +  2 × 10^(-3)
  auto length = 1.0_k + 2.0_m;

  printf("%lf\n", length);

}

しかし何か違う気がする。

C++には主に数値系で精度や範囲などを指定するsuffix(f,LLなど)と、文字列系で文字コードを指定するprefix(u8"hello"など)があるわけだが、両方とも定数の解釈方法を指定しているだけで、別にその定数に対して演算をしているのとは違う(気がする)。

だからどちらかというと以下のような使い方のほうが本来の用途に近いのでは...?と思う。

class Radian {
  double value;
public:
  Radian(const Radian& rad) { value = rad.value; }
  Radian(double value) { this->value = value; }
};
class Degree {
  double value;
public:
  Degree(const Degree& deg) { value = deg.value; }
  Degree(double value) { this->value = value; }
};


namespace suffix {

  Radian operator"" _rad(const long double value) { 
    return Radian( value ); 
  }

  Degree operator"" _deg(const long double value) { 
    return Degree( value ); 
  }
}


int main()
{
  using namespace suffix;

  Radian rad = 1.0_rad;
  Degree deg = 1.0_deg;

  // エラー
  // Radian rad2 = 1.0_deg;


}

つまりキャストのようなものだ。しかしこれでは rad=1.0;としても通ってしまう。別に間違ってはいないがせっかく使うならミスを減らせるような機能にしたい。

というわけで以下のようにしてみる。これで単位が不明な浮動小数点をうっかり代入してしまうのを防ぐことができた。

#include <iostream>

class Radian;
class Degree;

namespace suffix {

  Radian operator"" _rad(const long double value);
  Degree operator"" _deg(const long double value);
}


class Radian {
  double value;
private:
  Radian(double value) { this->value = value; }// ユーザー定義リテラルからしか呼び出せないようにする
public:
  Radian(const Radian& rad) { value = rad.value; }

  // ユーザー定義リテラルがコンストラクタへアクセスするために必要
  friend Radian suffix::operator"" _rad(const long double value);

  operator double()const { return value; }
};
class Degree {
  double value;
private:
  Degree(double value) { this->value = value; }// ユーザー定義リテラルからしか呼び出せないようにする
public:
  Degree(const Degree& deg) { value = deg.value; }

  // ユーザー定義リテラルがコンストラクタへアクセスするために必要
  friend Degree suffix::operator"" _deg(const long double value);

  operator double()const { return value; }
};



namespace suffix {

  Radian operator"" _rad(const long double value) { 
    return Radian( value ); 
  }

  Degree operator"" _deg(const long double value) { 
    return Degree( value ); 
  }
}


int main()
{
  using namespace suffix;

  Radian rad = 2.5_rad;
  Degree deg = 1.0_deg;


  // エラー
  // Radian rad2 = 1.0_deg;

  // これもエラー
  //rad = 1.0;

  rad = 3.0_rad;


  std::cout << rad << std::endl;

}

心の声

キャストしろ。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: