HowardHinnant / date

A date and time library based on the C++11/14/17 <chrono> header
Other
3.15k stars 682 forks source link

constexpr support in VS 2015 #25

Closed HowardHinnant closed 7 years ago

HowardHinnant commented 8 years ago

Currently constexpr support is turned off in VS-2015 due to the lack of ability to forward declare constexpr functions.

This issue is to serve as a reminder to re-check constexpr settings with VS-2015 Update 2 (unsure of the timeline for that release). I have reason to believe we can start turning constexpr on for VS at that time (hopefully at least the C++11 parts).

davidhunter22 commented 8 years ago

I did try 2015 Update 1 to see if there was any progress at all. That actually led to a compiler crash, see https://connect.microsoft.com/VisualStudio/feedback/details/1980909

philsquared commented 8 years ago

Update 2 has been out for a while now (and Update 3 is available in RC!). Has anyone tried this yet?

gmcode commented 8 years ago

hi phil*phil, I've tried update 2, this library works with it. I haven't tried update 3 yet as I was waiting for final release. I will be sure to install update 3 as soon as it is released in final form.

philsquared commented 8 years ago

Thanks @gmcode (btw, that's +ᵖʰⁱˡ(phil) to you ;-) ). When you say, "this library works with it" do you specifically mean the constexpr support?

gmcode commented 8 years ago

Hi Phil, I meant this library works with cl.exe (at least VS2014 / MSC_VER 19.00.23918) but not with full constexpr support because that version of VS doesn't have full support as you know. VS Update 3 has better constexpr support I believe but to what level I'm not sure. I haven't tried Update 3 with this library. I suspect a patch will be needed at that time, but it should be a small one (if you're using Update 3 RC try it, patches welcome!) as full constexpr does work in the clang variants and g++ 6 including when they run on Windows.

philsquared commented 8 years ago

AFAIK update 2 had more constexpr support than update 1 (which had more than GM). For some values of "more", of course :-) Since I don't know what those values are (they never seem to be spelled out!) I was interested to see whether update 2 had enough. It sounds like not. I'm still trying to get our code base working with update 2 (well 2015 in general). Sadly I don't have the bandwidth to try update 3 yet while it's still in beta. I'd certainly be keen to hear from anyone else if they've tried it, though. We have an aging Date/DateTime abstraction (wrapping Boost.DateTime) that I'm looking to replace with std::chrono and this library in the near future. constexpr would be a nice-to-have but is not a pre-req for our adoption so it's just general interest at this stage.

gmcode commented 8 years ago

yes update 2's constexpr isn't that great. If it helps, if you look at date.h line 53, VS goes through the first path. I am pretty confident MS are testing their constexpr support with this library though, so my bet is that very soon (in VS Update 3) VS will be easily persuaded to go through the 3rd path (C++11). If we are really lucky, maybe the 2nd path (C++14) might happen for VS 3 but I'm not so confident there. But clang-cl and clang are already doing the 3rd path on Windows so you can experiment quite a bit here (especially if you build it from git).

philsquared commented 8 years ago

Yeah, I've heard that Update 3 now builds boost with no special casing! That's promising. I'm hoping to start trying this lib out soon (with Update 2 - but Update 3 when it is officially released). constexpr would be nice :-) but not essential.

damian123 commented 8 years ago

How did you get that to compile in Visual Studio 2015 update 3?

using namespace date;
using namespace std::chrono;    
auto tp = sys_days{ 2016y / may / 29d } +7h + 30min + 6s + 153ms; // 2016-05-29 07:30:06.153 UTC

error C3688: invalid literal suffix 'y'; literal operator or literal operator template 'operator ""y' not found

HowardHinnant commented 8 years ago

Because this is not a standard library, and because the standard reserves the user-defined-literal y for itself, this library uses _y. Try 2016_y and report back if that still gives you trouble.

I am proposing this library for standardization, and if that happens, 2016y will be the std-syntax.

damian123 commented 8 years ago

I think you aim for at least 1900 which is Visual Studio 2015 MSVC++ 5.0 _MSC_VER == 1100 MSVC++ 6.0 _MSC_VER == 1200 MSVC++ 7.0 _MSC_VER == 1300 MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio 2003) MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)

damian123 commented 8 years ago

_ (underscore) works!

HowardHinnant commented 8 years ago

We've just recently back-ported to VS-2013, downgrading functionality like constexpr of course.

_MSC_VER == 1900 is known to require the constexpr settings as currently configured. But as far as I know, no one has tested VS-2015 update 3 in this regard.

ta1meng commented 7 years ago

We ran into a significant issue with constexpr compatibility recently and I think this might be the right thread in which to report the issue? If not please advise, and I'll file a separate ticket, or tickets.

We found that int64 provided insufficient range for processing date time values from at least one popular database format: Oracle, which supports date time values with nanosecond resolution. We thought Boost's int128_t would plug in as a template type, and just work.

Consider:

using duration_nano128 = std::chrono::duration<boost::multiprecision::int128_t, std::nano>;
using unzoned_nano128 = date::local_time<duration_nano128>;
using utc_nano128_no_leap_second = date::sys_time<duration_nano128>;

This seems ok for the most part. But, a call to make_time() fails to compile due to constexpr dependency:

void int128_t_test()
{
   using namespace std::chrono_literals;
   utc_nano128_no_leap_second utc128_datetime = utc_days{ date::jan / 1 / date::year(1970) } +7h +33min + 20s + 500ns;
   utc_days utc_daypoint = date::floor<date::days>(utc128_datetime);
   date::year_month_day ymd(utc_daypoint);
   duration_nano128 utc_timeOfDay = utc128_datetime - utc_daypoint;
   auto tod = date::make_time(utc_timeOfDay); // fails to compile due to constexpr dependency
}

We found a workaround but wonder if this section in date.h is ifdef'ed properly?

// #if !defined(_MSC_VER) || (_MSC_VER >= 1900)
// 
// template <class Duration>
// struct classify_duration
// {
//     static CONSTDATA classify value =
//         Duration{1} >= days{1}                 ? classify::not_valid :
//         Duration{1} >= std::chrono::hours{1}   ? classify::hour :
//         Duration{1} >= std::chrono::minutes{1} ? classify::minute :
//         Duration{1} >= std::chrono::seconds{1} ? classify::second :
//                                                  classify::subsecond;
// };
// 

Specifically it seems misaligned with the ifdef at the top of date.h?

#if defined(_MSC_VER) && ! defined(__clang__)

which seems to say "if using Visual Studio, don't use constexpr". But the section I commented out above said "if using Visual Studio and it is sufficiently new, enable more efficient code that has constexpr dependency".

Is it reasonable to ask that support be maintained for numeric classes that are not fully constexpr?

Ideally, the default behavior of the libraries contain this support, to back up the advertisement that we can plug in any numeric class as the duration template type and the date and timezone libraries will just work.

HowardHinnant commented 7 years ago

This is as good a place as any, thanks.

I don't have a good way to experiment with VS, so I'm depending on people such as yourself to help guide the VS port. I think the thought with this #if is that CONSTDATA will be const here if we're doing VS. But VS-2015 has enough constexpr mojo to do chrono::duration comparisons at compile time (that's my theory, not a fact).

On supporting non-constexprd representations, that's a good thought and I haven't experimented with that. I'm gleaning from your post that if you had a way to control this one #if, (more elegantly than commenting it out) then your non-constexprd representation just works? If so, that sounds pretty compelling to me.

But I also have to ask: You need nanosecond precision beyond +/-292 years?! I understand the need to be compatible with other systems (such as Oracle). Do they use 128 bits? Or do they use something else, such as double? Or are you running into overflow because they are using milliseconds (for example) out past +/-292 years, and you're converting that to nanoseconds once you get into chrono?

If the reasons are one of the latter, I can offer better ways to cope than using 128 bits everywhere.

Fwiw, you're in good company. I've seen at least one huge company go to 128 bit chrono::nanoseconds, but then they reversed that decision. Basically programmers are used to a single-precision time library (such as the C/POSIX API) and are not used to dealing with multi-precision libraries such as chrono and so there's a learning curve to come up. I'm currently working on a paper (and maybe a presentation) to help come up that learning curve.

ta1meng commented 7 years ago

We found a way to write the code in a way that appears to auto select the desired implementation, by first checking for constexpr dependency. This seems to get rid of the need to have an ifdef for classify_duration. What do you think?

 template <class Duration>
 struct classify_duration
 {
    static CONSTDATA classify value =
       noexcept(Duration{ 1 } >= days{ 1 }) ?
       (
          Duration{ 1 } >= days{ 1 } ? classify::not_valid :
          Duration{ 1 } >= std::chrono::hours{ 1 } ? classify::hour :
          Duration{ 1 } >= std::chrono::minutes{ 1 } ? classify::minute :
          Duration{ 1 } >= std::chrono::seconds{ 1 } ? classify::second :
          classify::subsecond) : 
       (
         std::ratio_greater_equal<
             typename Duration::period,
             days::period >::value
                 ? classify::not_valid :
         std::ratio_greater_equal<
             typename Duration::period,
             std::chrono::hours::period>::value
                 ? classify::hour :
         std::ratio_greater_equal<
             typename Duration::period,
             std::chrono::minutes::period>::value
                 ? classify::minute :
         std::ratio_greater_equal<
             typename Duration::period,
             std::chrono::seconds::period>::value
                 ? classify::second :
                 classify::subsecond);
 };

RE: "But I also have to ask: You need nanosecond precision beyond +/-292 years?! I understand the need to be compatible with other systems (such as Oracle). Do they use 128 bits? Or do they use something else, such as double? Or are you running into overflow because they are using milliseconds (for example) out past +/-292 years, and you're converting that to nanoseconds once you get into chrono?"

These are probing questions and I'll answer them the best I can. According to Oracle doc http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements001.htm#i54330, (or http://wiki.ispirer.com/sqlways/oracle/data-types/timestamp for improved readability), we see that Oracle needs 11 bytes for TIMESTAMP values with nanosecond precision. The minimally required number of bits to store Oracle's supported range is more than 64 and less than 128.

Next, do our customers have date time data with 9 fractional digits? With high likelihood, no. But we are an ETL tool and not in a position to question why Oracle chose to support a range of thousands of years at nanosecond resolution, or why SQL Server recently decided to support thousands of years at the 100 nanosecond resolution in it DATETIME2 type. Our position is to provide our users with a date time model that accommodates their date time values from databases.

With a survey we found 15+ user tickets for fractional second support and 10+ user tickets for improved date time arithmetic. Right now, our Oracle reader reads at the nanosecond resolution, our Oracle writer writes at the microsecond resolution, and our arithmetic tools have no fractional second support. We are hoping to overcome all these staring issues, all in the same release cycle. I was so very thrilled to see your libraries at CppCon! By the way, we made a lot of progress on using Boost's int128_t as the duration template type and I'd be happy to take this conversation offline (e.g. email) because this is not strictly on topic in the context of this thread.

HowardHinnant commented 7 years ago

noexcept(Duration{ 1 } >= days{ 1 }) is never true with libc++. On libstdc++ and VS it is true if the rep >= is noexcept, however that doesn't guarantee that Duration{ 1 } >= days{ 1 } can be a compile-time value.

It does sounds like you need a 128 bit nanoseconds to me. And I'd like date.h to work for you in this department. I'll make it happen.

HowardHinnant commented 7 years ago

I believe this has been addressed. Please reopen if I am incorrect.