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


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


# そのためのコード
```cpp: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()` を記述します。
```cpp: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()` 関数は、引数で与えられた日付けを「過ぎない限り偽を返す」ので、このように書いておけば：
```cpp
static_assert(is_past_the_date(年, 月, 日) == false, "※ここに必要な情報を書く※");
```
引数の「年月日」を過ぎたら、コンパイラがエラーを起こすので、そこで自分がやるべきことを思い出す、というわけです。

#### サンプルコード
``` cpp: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 コメント代わりだから）、このような
```cpp
static_assert(is_past_the_date(2022,  5, 45) == false,  "...");
```
あり得ない日付けを書いても普通にコンパイルできてしまいます。


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

私の場合は `__has_include` を使って、
```cpp
#if __has_include("自分のところにしか無い、と確信できるファイル名")
...
// ここに、自分専用のラップ関数を記述するなどして、対応しておく
...
#else
...
// ここは、自分の環境以外でコンパイルされるので、
// is_past_the_date() がいつでも偽を返すなどの仕掛けをしておく
...
#endif
```
こんな感じで運用していました。
（まあ __has_include をこういう目的で使うのはどうかとも思うので、他に良さそうな解決策を見つけたいと思っています）



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