Get current month index in compile time
First: are you sure you need this at compile-time? If run-time is acceptable it's easy: http://www.keil.com/support/docs/1102.htm
But moving away from what's sane, let's have some compile-time fun!
You're using templates here but you really don't need to. You can use a massive expression of doom instead:
static const char c0 = __DATE__[0];
static const char c1 = __DATE__[1];
static const char c2 = __DATE__[2];
static const unsigned int month = (
c0 == 'J' // Jan Jun Jul
? (c1 == 'a' ? 1 : (c2 == 'n' ? 6 : 7))
: c0 == 'F' ? 2
: c0 == 'M' // Mar May
? (c2 == 'r' ? 3 : 5)
: c0 == 'A' // Apr Aug
? (c1 == 'p' ? 4 : 8)
: c0 == 'S' ? 9
: c0 == 'O' ? 10
: c0 == 'N' ? 11
: 12
);
Disclaimer: I just wrote that off the top of my head. It works now, but who knows, maybe I got March wrong.
In fact if you want to get even more fun* we can use arithmetic on some characters:
static const char c0 = __DATE__[0];
static const char c1 = __DATE__[1];
static const char c2 = __DATE__[2];
static const unsigned int month = (
c0 == 'J' // Jan Jun Jul
? (c1 == 'a' ? 1 : (c2 == 'n' ? 6 : 7))
: c0 == 'M' // Mar May
? (3 + (c2 == 'y') * 2)
: c0 == 'A' // Apr Aug
? (4 + (c1 == 'u') * 4)
: c0 == 'S' ? 9
: c0 <= 'F' ? (12 - (c0 - 'D') * 5) // Feb, Dec
: (11 + 'N' - c0) // Oct, Nov
);
*: by "fun" I mean: hated by other developers
Since these are const, you can then use it with templates. For example, suppose we've got a contract job which ends in November, and we want to be sure we'll get brought back in for a few days at a high rate once it's over:
#include <iostream>
using namespace std;
static const unsigned int month = ...;
template <int n> class mm {
public:
static int v;
};
template<> int mm<9>::v=3; // still employed
template<> int mm<10>::v=2; // let's not be too suspicious
template<> int mm<11>::v=1; // patience...
// no value for December - boom! we're in the money! Just in time for Christmas!
int main() {
std::cout << mm<month>::v;
return 0;
}
Finally, if you don't want to be littering the global scope, you should use a constexpr function:
static constexpr int getMonth( void ) {
const char c0 = __DATE__[0];
const char c1 = __DATE__[1];
const char c2 = __DATE__[2];
return (
c0 == 'J' // Jan Jun Jul
? (c1 == 'a' ? 1 : (c2 == 'n' ? 6 : 7))
: c0 == 'F' ? 2
: c0 == 'M' // Mar May
? (c2 == 'r' ? 3 : 5)
: c0 == 'A' // Apr Aug
? (c1 == 'p' ? 4 : 8)
: c0 == 'S' ? 9
: c0 == 'O' ? 10
: c0 == 'N' ? 11
: 12
);
}
...
std::cout << mm<getMonth()>::v;
Just having fun with this here ...
My answer requires C++14 and a few external libs, but demonstrates that fairly amazing compile-time computations are available in C++14.
First I need Scott Schurr's str_const
presented at C++ Now 2012. This class is a compile-time string and discussed a bit in this answer.
Then I need this date/time library which is capable of compile-time date and time computations.
Next I need a constexpr
implementation of std::find
:
template <class InputIterator, class T>
constexpr
inline
InputIterator
find(InputIterator first, InputIterator last, const T& value)
{
for (; first != last; ++first)
if (*first == value)
break;
return first;
}
With that I can write str_to_month
which takes a str_const
and turns it into a date::month
:
constexpr
date::month
str_to_month(const str_const& m)
{
constexpr
str_const months[]
{
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
auto i = ::find(std::begin(months), std::end(months), m);
if (i == std::end(months))
throw std::range_error("str_to_month received out of range argument " +
std::string(m));
return date::month{static_cast<unsigned>(i - std::begin(months)) + 1};
}
Next I need a utility to convert a str_const
into an int
:
constexpr
int
str_to_int(const str_const& s)
{
int r = 0;
auto i = s.begin();
for (; i != s.end() && *i == ' '; ++i)
;
for (; i != s.end(); ++i)
{
r *= 10;
r += *i - '0';
}
return r;
}
(with minimal error checking)
And finally I can use these utilities to turn a str_const
into a date::year_month_day
:
// Assume the form used by __DATE__: Mmm dd yyyy
constexpr
date::year_month_day
str_to_year_month_day(const str_const& s)
{
return str_to_month(s.substr(0, 3))
/str_to_int(s.substr(4, 2))
/str_to_int(s.substr(7));
}
I just exercised all this with the following main
, which computes everything with constexpr
, and confirms the computation with static_assert
:
int
main()
{
constexpr auto ymd = str_to_year_month_day(__DATE__);
using namespace date;
static_assert(ymd == sep/6/2015, "");
constexpr auto ymwd = year_month_weekday{ymd};
static_assert(ymwd == sun[1]/sep/2015, "");
}
I compiled this program on Sep. 6, 2015 which happens to be the first Sunday of this month.
You'll need gcc or clang to do this. Even the latest VS-2015 isn't up to spec with constexpr
enough to do these computations at compile-time.