1

I made a template function for initializing a chrono::time_point from a number. I have succeeded so far but encountered an issue which I do not fully understand. Two minimal examples of my code are given below.

Below code fails to compile with the following error:

/usr/include/c++/7/chrono:616:14: note:   no known conversion for argument 1 from ‘const double’ to ‘const std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<double> >&’
/usr/include/c++/7/chrono:616:14: note: candidate: constexpr std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<double> >::time_point(std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<double> >&&)
/usr/include/c++/7/chrono:616:14: note:   no known conversion for argument 1 from ‘const double’ to ‘std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<double> >&&’
#include <iostream>
#include <chrono>

namespace yv {
using clock_t = std::chrono::system_clock;
using duration_t = std::chrono::duration<double>;
using time_t = std::chrono::time_point<clock_t, duration_t>;

namespace fromnumber {
template<class T, class T_time> T_time time(T const& timestamp) {
    return T_time(timestamp);
}
// No specialization

}; // end namespace fromnumber
}; // end namespace yv


int main()
{


    using namespace yv;
    using namespace std;

    yv::time_t t0 = yv::fromnumber::time<double, yv::time_t>(0.0);
    yv::time_t t1 = yv::fromnumber::time<double, yv::time_t>(1548675254.0);

    return 0;
}

However, when I add a template specialization with empty definition it does compile.

#include <iostream>
#include <chrono>

namespace yv {
using clock_t = std::chrono::system_clock;
using duration_t = std::chrono::duration<double>;
using time_t = std::chrono::time_point<clock_t, duration_t>;

namespace fromnumber {
template<class T, class T_time> T_time time(T const& timestamp) {
    return T_time(timestamp);
}

template<> std::chrono::time_point<clock_t, duration_t> time(double const&) {
// EMPTY
}

}; // end namespace fromnumber
}; // end namespace yv


int main()
{


    using namespace yv;
    using namespace std;

    yv::time_t t0 = yv::fromnumber::time<double, yv::time_t>(0.0);
    yv::time_t t1 = yv::fromnumber::time<double, yv::time_t>(1548675254.0);

    return 0;
}

The specialization has a definition but it does not even return a value. What am I missing here?

EDIT: Thanks for the quick replies. Below is a more extensive example using Howard Hinnant's date.h.

#include <iostream>

#include "date/date.h"
//#include <chrono>

//using namespace date;


namespace yv {
using clock_t = std::chrono::system_clock;
using duration_t = std::chrono::duration<double>;
using time_t = std::chrono::time_point<clock_t, duration_t>;

namespace fromnumber {
template<class T, class T_time> T_time time(T const& timestamp) {
    return T_time(timestamp);
}

// Case 1. Correct specialization, not getting any warnings.
template<> std::chrono::time_point<clock_t, duration_t> time(double const& t)
{
    return std::chrono::time_point<clock_t, duration_t>(duration_t(t));
}

// Case 2. Incorrect specialization, compiles and prints the correct datetime but getting a warning
template<> std::chrono::time_point<clock_t, duration_t> time(double const& t)
{
}

// Case 3. Without the specialization it will not compile, error given above


}; // end namespace fromnumber
}; // end namespace yv

std::ostream& operator<< (std::ostream& outStream, const yv::time_t& t) {
    using namespace date;
    auto t2 = date::floor<std::chrono::milliseconds>(t);
    outStream << date::format("%c", t2);
    return outStream;
}


int main()
{


    using namespace yv;
    using namespace std;

    yv::time_t t0 = yv::fromnumber::time<double, yv::time_t>(0.0);
    yv::time_t t1 = yv::fromnumber::time<double, yv::time_t>(1548675254.0);

    cout << t1 << endl;
    // expecting: Mon Jan 28 11:34:14 2019
    return 0;
}

Warning in case 2:

../try_chrono/main.cpp: In function ‘T_time yv::fromnumber::time(const T&) [with T = double; T_time = std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<double> >]’:
../try_chrono/main.cpp:21:1: warning: no return statement in function returning non-void [-Wreturn-type]
 }
 ^

It is clearly undefined behavior to not return a value from a non-void function. However, the thing I do not understand is how it can be possible that I am getting the correct output with an empty specialization? The way I see it is that both case 2 and case 3 are incorrect and should not give me a correct result on stdout.

Robert
  • 119
  • 8
  • Undefined Behavior does not mean "will definitely give the wrong result". It just means "anything (including 'correct' behavior) can happen". – Max Langhof Jan 28 '19 at 13:09

3 Answers3

3

Undefined behavior. It compiles, you will have a giant warning though, and perhaps a crash. Or nothing.

The thing you know is that you need this definition, and as the definition says, it should return a std::chrono::time_point<clock_t, duration_t>. If you don't then you are breaking your contract. The compiler says so:

warning: no return statement in function returning non-void [-Wreturn-type]
Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
  • Thanks for your effort, it is indeed undefined behavior and is highly dependent on platform. – Robert Jan 28 '19 at 13:55
3

The template specialization does not return anything when it is supposed to return std::chrono::time_point<clock_t, duration_t>, resulting in undefined behavior.

The standard clearly states this in [stmt.return]/2 :

Flowing off the end of a value-returning function (except main) without a return statement is undefined behavior.

P.W
  • 26,289
  • 6
  • 39
  • 76
  • I updated my question with a more extensive example. With the empty specialization I am still getting correct results. This does not make sense to me. Is the compiler not able to fill in the correct return type from the call in `main()`? – Robert Jan 28 '19 at 12:42
  • 1
    UB means anything can happen including what is normally expected behavior. No requirements are imposed. – P.W Jan 28 '19 at 12:43
0

After stepping through the code with a debugger I found that the empty definition was returning the argument after compilation. The compiler was effectively changing this:

template<> std::chrono::time_point<clock_t, duration_t> time(double t)
{
}

into this:

template<> std::chrono::time_point<clock_t, duration_t> time(double t)
{
return (std::chrono::time_point<clock_t, duration_t>) t;
}

Which in turn resulted in a "correct" binary because an instance of type std::chrono::time_point<clock_t, duration_t> looks like this in memory:

name        value          address
t0                         @0x0123456789ab
    __d                    @0x0123456789ab
        __r 1548675254.02  @0x0123456789ab

so the assignment is performed correctly. However, with a non-empty specialization function without return argument this quirk breaks. For example the following function does not return (std::chrono::time_point<clock_t, duration_t>) t:

template<> std::chrono::time_point<clock_t, duration_t> time(double t)
{
    cout << t << endl;
}

https://stackoverflow.com/a/1610454/2548426 According to this answer the resulting binary depends on platform, architecture and compiler.

As the previous answers have stated, it is undefined behavior. It is now clear me what was causing the apparent correct result.

The correct specialization:

template<> std::chrono::time_point<clock_t, duration_t> time(double t)
{
    return std::chrono::time_point<clock_t, duration_t>(duration_t(t));
}

or

template<> time_t time(double t)
{
    return time_t(duration_t(t));
}
Robert
  • 119
  • 8