Search code examples
c++visual-c++c++20constexpr

"constexpr" and std::to_string in C++ 20


I have the following declaration -

constexpr unsigned int compileYear = (__DATE__[7] - '0') * 1000 + (__DATE__[8] - '0') * 100 + (__DATE__[9] - '0') * 10 + (__DATE__[10] - '0');

Using Visual Studio 2022 and C++ 20, this function cannot compile:

constexpr std::string get_compilation_year()
{
    return std::to_string(compileYear); // cannot result in a constant expression 
}

And for some reason, adding an empty string to it, makes it compile successfully:

constexpr std::string get_compilation_year()
{
    return std::to_string(compileYear) + ""; // OK, it works 
}

I want to understand the reason for that behavior of the compiler.


Solution

  • std::to_string(int) is not constexpr so you will not be able to initialize a constexpr variable via a call to get_compilation_year() even if it compiles in its current state.

    You could use one of the constexpr std::string constructors instead:

    template< class InputIt >
    constexpr std::string(InputIt first, InputIt last,
                          const Allocator& alloc = Allocator() );
    

    or

    constexpr std::string(std::initializer_list<char> ilist,
                          const Allocator& alloc = Allocator() );
    

    Currently, only the one that takes an std::initializer_list<char> works in this context in MSVC, gcc and clang. Clang doesn't like the iterator version (yet).

    So, a (somewhat) portable C++20 version could look like this:

    constexpr std::string get_compilation_year() {
        return {__DATE__[7], __DATE__[8], __DATE__[9], __DATE__[10]};
    }
    

    Or as suggested by Pepijn Kramer, make it a std::string_view instead:

    constexpr std::string_view get_compilation_year() {
        return {__DATE__ + 7, 4};
    }
    

    The somewhat portable version works as long as there's no allocation made by the constructor. For a longer text including the current year, you could initialize a std::array and create a std::string_view over it:

    #include <array>
    #include <string_view>
    #include <utility>
    
    constinit auto copyright_data = [] {
        auto& beg = "Copyright (C) XXXXX 1986 - ";
        return [&]<std::size_t... I>(std::index_sequence<I...>) {
            return std::array{beg[I]..., __DATE__[7], __DATE__[8], __DATE__[9],
                              __DATE__[10]};
        }(std::make_index_sequence<sizeof(beg) - 1>{});
    }();
    
    constexpr std::string_view copyright_text{copyright_data.begin(),
                                              copyright_data.end()};
    

    Demo