3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C++ コンパイラに ToDo を教えてもらう

Last updated at Posted at 2022-06-17

「あとで着手すること」を、スケジューラや、ソースファイルの ToDo コメントに頼らずに、コンパイラに教えてもらうようにしてみました。

そのためのコード

check_date.hpp

#pragma once

// #include <stdlib.h> // ※ 多分大丈夫かとは思いますが、コンパイラによっては、これが必要になる場合がありましたので、参考用に置いておきます。

template<char, char, char>
struct TypeMonthValue
{
  enum { Val = 0 };
};

#define DEFINE_MONTH_VALUE(monthString, monthValue) \
template<> \
struct TypeMonthValue<monthString[0], monthString[1], monthString[2]> \
{ \
  enum { Val = monthValue }; \
}; \

DEFINE_MONTH_VALUE("Jan",  1);
DEFINE_MONTH_VALUE("Feb",  2);
DEFINE_MONTH_VALUE("Mar",  3);
DEFINE_MONTH_VALUE("Apr",  4);
DEFINE_MONTH_VALUE("May",  5);
DEFINE_MONTH_VALUE("Jun",  6);
DEFINE_MONTH_VALUE("Jul",  7);
DEFINE_MONTH_VALUE("Aug",  8);
DEFINE_MONTH_VALUE("Sep",  9);
DEFINE_MONTH_VALUE("Oct", 10);
DEFINE_MONTH_VALUE("Nov", 11);
DEFINE_MONTH_VALUE("Dec", 12);

#undef DEFINE_MONTH_VALUE

#define NUMBERS_EXTRACT_FROM_DATE(index, multi) ((__DATE__[index]-0x30) * multi)

/// @brief コンパイル時に、日付チェックをするために使用するクラス
/// @note 関数実装に三項演算子を使用しているのは C++11 でもコンパイルできるようにするため
class CheckDate
{
public:
  constexpr CheckDate() : monthValue_(TypeMonthValue<__DATE__[0], __DATE__[1], __DATE__[2]>::Val)
  {}

  /// @brief 『本日』が、指定の日付を過ぎていたら、真を返す
  constexpr bool is_past_the_date(size_t year, size_t month, size_t day) const
  {
    return 
      is_past_the_year(year) ||
      (
        (numerical_of_year() < year) 
          ?
        false
          :
        ((numerical_of_month() >= month && numerical_of_day() > day) ||
         (numerical_of_month() >  month))
      );
  }

  /// @brief 「本日の『年』」を、数値で返す
  constexpr size_t numerical_of_year() const
  {
    return
      NUMBERS_EXTRACT_FROM_DATE( 7,  1000) +
      NUMBERS_EXTRACT_FROM_DATE( 8,   100) +
      NUMBERS_EXTRACT_FROM_DATE( 9,    10) +
      NUMBERS_EXTRACT_FROM_DATE(10,     1);
  }

  /// @brief 「本日の『日』」を、数値で返す
  constexpr size_t numerical_of_day() const
  {
    return
      __DATE__[4] == ' '  // 一桁の場合は [4] が空白なので、その判断をしている
        ?
      NUMBERS_EXTRACT_FROM_DATE(5, 1)
        :
      NUMBERS_EXTRACT_FROM_DATE(4, 10) +
      NUMBERS_EXTRACT_FROM_DATE(5,  1);
  }

  /// @brief 「本日の『月』」の数値を返す。例えば May なら 5 が返る
  constexpr size_t numerical_of_month() const
  {
    return monthValue_;
  }

  /// @brief 「本日の『年』」が、指定の year を過ぎていたら真を返す
  constexpr bool is_past_the_year(size_t year) const
  {
    return numerical_of_year() > year;
  }

private:
  const size_t monthValue_ = 0;

  CheckDate(const CheckDate&) = delete;
  CheckDate& operator= (const CheckDate&) = delete;
};

#undef NUMBERS_EXTRACT_FROM_DATE

// 使用するコード側で、CheckDate のインスタンスがある時に使う関数
inline constexpr bool is_past_the_date(const CheckDate& inst, size_t year, size_t month, size_t day)
{
  return inst.is_past_the_date(year, month, day);
}

// 通常の使用には、こちらの関数を使用すると良いです
inline constexpr bool is_past_the_date(size_t year, size_t month, size_t day)
{
  return is_past_the_date(CheckDate{}, year, month, day);
}

どのように使用するのか

このように、必要なところに static_assert() を記述します。

sample.cpp
#include "check_date.hpp"
...
void foo()
{
  // ※下の記述は 2022年6月17日 を過ぎると、コンパイルエラーになる
  static_assert(is_past_the_date(2022, 6, 17) == false, "この日付けが過ぎたら、ここの実装に着手するぞー");
}
...

説明

is_past_the_date() 関数の引数には、エラーが起きて欲しいタイミング(つまり、自分に対して「なにか、やることがあるみたいだよ?」と報せて欲しいタイミング)を書いておきます。

そして static_assert() 関数の第二引数には「やるべきこと」を書いておきます。

is_past_the_date() 関数は、引数で与えられた日付けを「過ぎない限り偽を返す」ので、このように書いておけば:

static_assert(is_past_the_date(, , ) == false, "※ここに必要な情報を書く※");

引数の「年月日」を過ぎたら、コンパイラがエラーを起こすので、そこで自分がやるべきことを思い出す、というわけです。

サンプルコード

main.cpp
#include "check_date.hpp"
int main()
{
  // 下の行は 2022/06/17 を過ぎると、コンパイルエラーになる
  static_assert(is_past_the_date(2022, 6, 17) == false, "error #1");

  // 下の行は 2022/06/16 を過ぎると、コンパイルエラーになる
  static_assert(is_past_the_date(2022, 6, 16) == false, "error #2");

  return 0;
}

上記コードを
2022年6月17日
にコンパイルすると

実行結果
error C2338: error #2

となります。

コード確認の環境

Visual Studio 2017 を使用。
  ターゲットプラットフォーム : Windows 10
  プラットフォームツールセット : Visual Studio 2017 (v141)

補足(注意事項)

is_past_the_date() 関数の引数は論理チェックをしていない

そこまで厳密な機能は要らないので(単なる ToDo コメント代わりだから)、このような

static_assert(is_past_the_date(2022,  5, 45) == false,  "...");

あり得ない日付けを書いても普通にコンパイルできてしまいます。

複数人でファイルを触っている場合は、他のメンバのところでエラーが起きないように予防しておく

static_assert(is_past_the_date(,,,) == false, "...");
を仕込む理由が「自分だけに関係する」場合は、他の人のところでエラーを起こしたくは無いと思います。
そのような場合は、自分の環境だけでエラーが起きるようにしておく必要があります。

私の場合は __has_include を使って、

#if __has_include("自分のところにしか無い、と確信できるファイル名")
...
// ここに、自分専用のラップ関数を記述するなどして、対応しておく
...
#else
...
// ここは、自分の環境以外でコンパイルされるので、
// is_past_the_date() がいつでも偽を返すなどの仕掛けをしておく
...
#endif

こんな感じで運用していました。
(まあ __has_include をこういう目的で使うのはどうかとも思うので、他に良さそうな解決策を見つけたいと思っています)

おわりに

多分、私が知らないだけで、似たようなのは誰かが作っていらっしゃると思います。
これを作った当時、色々なワードでネット検索しましたが見つからなかったので、自分で作ることにしたのです。
ただ、必要な機能を考えて、コードを書いている時は楽しかったので、(検索しても)見つけられなくて良かったかなあ、とも思っています。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?