Search code examples
c++c++11user-defined-literals

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?


Solution

  • 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.