9

I'm writing a user-defined string literal to convert names of months into their numbers. The expected usage of this literal is something like

"Nov"_m

which should return 11.

At the moment my code looks like

constexpr Duration operator ""_m(const char* str, size_t len)
{
    return convert_month_to_int(str, len);
}

where constexpr int convert_month_to_int(const char, size_t) is a function which does the actual conversion (or returns -1 if the month name is incorrect).

The problem is that I would like to show some kind of compile error if the string passed to this literal does not name any month. I tried using static_assert in the following way:

constexpr Duration operator ""_m(const char* str, size_t len)
{
    static_assert(convert_month_to_int(str, len) > 0, "Error");
    return convert_month_to_int(str, len);
}

but this does not work since the compiler is not sure that convert_month_to_int(str, len) will be a constant expression.

Is there any way of achieving this behavior?

alexeykuzmin0
  • 6,344
  • 2
  • 28
  • 51
  • gcc has extension to use template method here. – Jarod42 Aug 06 '16 at 09:44
  • @Jarod42 You mean the variadic template user-defined literals? Aren't they for integer and floating-point types only? – alexeykuzmin0 Aug 06 '16 at 09:46
  • 3
    Sorry for being stupid (the deleted answer) and all, but really, with just a small set of valid month identifiers, why not make it an `enum`? Because that's what `enum`s are for. Naming values, making a type out of it. – Cheers and hth. - Alf Aug 06 '16 at 09:47
  • From standard, yes, but gcc has extension for c-string too, which would allow to do your check, but it is an extension. – Jarod42 Aug 06 '16 at 09:47
  • @Cheersandhth.-Alf I already have literals for years, months as a number, days, etc., and I'm able to enter the date like this: `2016_y + "Aug"_m + 6_d + 12_h + 50_min`. I think, usage of `enum`s here would be less pretty. Probably, if this question has no good answer, I'll switch to `enum`s. – alexeykuzmin0 Aug 06 '16 at 09:51
  • How would that handle leap years? – Daniel Aug 06 '16 at 10:01
  • @Dani Not sure I understand you. Leap years should be handled inside the `operator +` – alexeykuzmin0 Aug 06 '16 at 10:03
  • 1
    `enum { Jan_m, Feb_m, ...` pretty! – n. m. could be an AI Aug 06 '16 at 10:11
  • 2
    It seems to me you are trying to make a function with parameters but using UDL syntax and `operator+`. Your example could be written as `make_date(2016, Aug, 6, 12, 50)`. The reason I think your way is flawed is because of questions like how much is March? 59 or 60? What is `2016_y + "Aug"_m + "December"_m`? It seems like there is only one way to order your expressions and you are better off with a function call. – Daniel Aug 06 '16 at 10:12
  • With the gnu extension, it would be something like : [Demo](http://coliru.stacked-crooked.com/a/9bd7745439c9e741) – Jarod42 Aug 06 '16 at 13:33

2 Answers2

3

I agree with suggestion to use an enum instead.

But anyways, the usual way to signal an error like this in a constexpr function is to throw an exception.

constexpr Duration operator ""_m(const char* str, size_t len)
{
    return convert_month_to_int(str, len) > 0 ? convert_month_to_int(str, len) : throw "Error";
}

See also this question for instance.

Community
  • 1
  • 1
Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • Unfortunately, this will produce a compile-time error only when the result of `"NotAMonth"_m` is assigned to a `constexpr`. – alexeykuzmin0 Aug 06 '16 at 11:16
  • @alexeykuzmin0 - that is: this will produce a compile-time error only when the error is detected at compile time; I don't know a way to impose a compile-time error when the error is detected run-time – max66 Aug 06 '16 at 11:26
  • I don't see any solution to that unfortunately. The issue is that in your operator `""` implementation, `const char * str` and `size_t len` are not constant expressions. (see, same question, [Columbo's answer](http://stackoverflow.com/questions/27291903/passing-constexpr-objects-around)). So I don't think you can use them in a `static_assert` condition. Unless you force the operator to run at compile-time, it will be too late to produce a compile-time error. The simplest thing is really to use an enum. – Chris Beck Aug 06 '16 at 11:26
3

I've approached this problem in a different way, using neither enums nor string literals, and bad month names are detected even when not constructed as constexpr:

#include "date.h"

int
main()
{
    using namespace date::literals;
    auto m1 = nov;                           // ok
    static_assert(unsigned{nov} == 11, "");  // ok
    auto m2 = not_a_month;
    test.cpp:86:15: error: use of undeclared identifier 'not_a_month'
        auto m2 = not_a_month;
                  ^
    1 error generated.
}

The approach I used is to define a class type month which is documented to be a literal class type.

I then create constexpr instances of each month:

CONSTDATA date::month jan{1};
CONSTDATA date::month feb{2};
CONSTDATA date::month mar{3};
CONSTDATA date::month apr{4};
CONSTDATA date::month may{5};
CONSTDATA date::month jun{6};
CONSTDATA date::month jul{7};
CONSTDATA date::month aug{8};
CONSTDATA date::month sep{9};
CONSTDATA date::month oct{10};
CONSTDATA date::month nov{11};
CONSTDATA date::month dec{12};

(CONSTDATA is a macro to help compilers which aren't quite there with C++11 constexpr support limp along)

I also used the same technique for days of the week.

The above was all compiled using clang with -std=c++11. It will also work with gcc. The constexpr bits are broken in VS, but everything else works, including detecting bad month names at compile time.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577