alexeykuzmin0
alexeykuzmin0

Reputation: 6440

Is there any way for compile-time check of string user-defined literal?

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?

Upvotes: 9

Views: 770

Answers (2)

Howard Hinnant
Howard Hinnant

Reputation: 218700

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.

Upvotes: 3

Chris Beck
Chris Beck

Reputation: 16204

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.

Upvotes: 3

Related Questions