「あとで着手すること」を、スケジューラや、ソースファイルの ToDo コメントに頼らずに、コンパイラに教えてもらうようにしてみました。
そのためのコード
#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()
を記述します。
#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, "※ここに必要な情報を書く※");
引数の「年月日」を過ぎたら、コンパイラがエラーを起こすので、そこで自分がやるべきことを思い出す、というわけです。
サンプルコード
#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 をこういう目的で使うのはどうかとも思うので、他に良さそうな解決策を見つけたいと思っています)
おわりに
多分、私が知らないだけで、似たようなのは誰かが作っていらっしゃると思います。
これを作った当時、色々なワードでネット検索しましたが見つからなかったので、自分で作ることにしたのです。
ただ、必要な機能を考えて、コードを書いている時は楽しかったので、(検索しても)見つけられなくて良かったかなあ、とも思っています。