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.

Tags:

C++

Visual C++