【问题标题】:Template specialization with empty definition具有空定义的模板特化
【发布时间】:2019-01-28 12:14:40
【问题描述】:

我制作了一个模板函数,用于从数字初始化chrono::time_point。到目前为止我已经成功了,但遇到了一个我不完全理解的问题。下面给出了我的代码的两个最小示例。

以下代码无法编译并出现以下错误:

/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;
}

但是,当我添加具有空定义的模板特化时,它会编译。

#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;
}

特化有一个定义,但它甚至不返回一个值。我在这里错过了什么?

编辑: 感谢您的快速回复。下面是一个使用 Howard Hinnant 的 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;
}

情况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]
 }
 ^

不从非 void 函数返回值显然是未定义的行为。但是,我不明白的是,我怎么可能通过空的专业化获得正确的输出? 我看到它的方式是 case 2 和 case 3 都不正确,不应该在 stdout 上给我正确的结果。

【问题讨论】:

  • 未定义行为并不意味着“肯定会给出错误的结果”。它只是意味着“任何事情(包括‘正确’行为)都可能发生”。

标签: c++ chrono function-templates


【解决方案1】:

未定义的行为。它编译,你会得到一个巨大的警告,也许会崩溃。或者什么都没有。

你知道你需要这个定义,正如定义所说,它应该返回一个std::chrono::time_point&lt;clock_t, duration_t&gt;。如果你不这样做,那么你就违反了合同。编译器是这样说的:

warning: no return statement in function returning non-void [-Wreturn-type]

【讨论】:

  • 感谢您的努力,这确实是未定义的行为,并且高度依赖于平台。
【解决方案2】:

模板特化在它应该返回std::chrono::time_point&lt;clock_t, duration_t&gt;时没有返回任何东西,导致未定义的行为。

标准在[stmt.return]/2 中明确说明了这一点:

在没有返回语句的情况下从返回值函数(main 除外)的末尾流出是未定义的行为。

【讨论】:

  • 我用一个更广泛的例子更新了我的问题。有了空的专业化,我仍然得到正确的结果。这对我来说没有意义。编译器是否无法从main() 中的调用中填写正确的返回类型?
  • UB 意味着任何事情都可能发生,包括通常预期的行为。没有任何要求。
【解决方案3】:

在使用调试器单步执行代码后,我发现空定义在编译后返回参数。编译器有效地改变了这一点:

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

进入这个:

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

这又导致了一个“正确”的二进制文件,因为 std::chrono::time_point&lt;clock_t, duration_t&gt; 类型的实例在内存中看起来像这样:

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

因此分配正确执行。 但是,对于没有返回参数的非空特化函数,这个怪癖就被打破了。 例如下面的函数不返回(std::chrono::time_point&lt;clock_t, duration_t&gt;) t

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

https://stackoverflow.com/a/1610454/2548426 根据这个答案,生成的二进制文件取决于平台、架构和编译器。

正如前面的答案所述,这是未定义的行为。现在我很清楚是什么导致了明显的正确结果。

正确的专业化:

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

template<> time_t time(double t)
{
    return time_t(duration_t(t));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-20
    • 2018-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多