std::chrono::duration::duration()怎么能成为constexpr?

18
std::chrono::duration 的默认构造函数定义如下: constexpr duration() = default; (例如,参见 cppreference.com 或 libstdc++ 源代码。)
然而,cppreference.com 关于 constexpr 构造函数也有如下说明:

constexpr 构造函数必须满足以下要求:

...

每个基类和每个非静态成员都必须在构造函数初始化列表中或通过成员大括号等号初始化器进行初始化。此外,涉及的每个构造函数都必须是 constexpr 构造函数,并且每个大括号等号初始化器的每个子句都必须是常量表达式。

如果我对默认构造函数感到困惑,cppreference.com 似乎表明使用 = default 带来的默认构造函数与隐式默认构造函数没有不同之处。
然而,(大多数)持续时间的rep类型是裸整型。 因此,duration 的显式默认构造函数= default是否应该等同于 constexpr duration() {} 当然,这会导致duration::rep类型的整数成员变量未初始化?实际上,duration的标准行为难道不是默认构造值未初始化吗?(但我找不到明确说明此事的参考文献。)
那么,如果将= default构造函数用于duration,它如何能够是constexpr?因为它会导致非静态成员变量未初始化?我错过了什么吗?

这是在temploid上的constexpr,因此即使默认版本不满足constexpr构造函数的要求,它仍然可以使用 - 只是不能用于常量表达式。 - T.C.
duration::rep 是一种类型,而不是成员,对吧? - AndyG
@T.C. 什么是模板化? - TemplateRex
1
@TemplateRex 模板外的类似模板的东西(例如,类模板的非模板成员函数/类)。 - T.C.
@AndyG:是的,我澄清了措辞,以显示我的意思是必须存在类型为duration::rep但名称未指定的成员变量。 - Brad Spencer
显示剩余3条评论
1个回答

15

7.1.5的constexpr指示符[dcl.constexpr]表示:

constexpr构造函数的定义应满足以下要求:

  • 类不得有任何虚基类;
  • 对于默认复制/移动构造函数,其类不得有一个可变的子对象,该子对象是一个变体成员;
  • 每个参数类型都必须是字面类型;
  • 其函数体不得是函数尝试块;

此外,它的函数体应为= delete,或者它应满足以下要求:

  • 其函数体应为= default,或其函数体的复合语句应符合constexpr函数的函数体要求;
  • 每个非变体非静态数据成员和基类子对象都应初始化(12.6.2);
  • 如果类是具有变体成员(9.5)的联合体,则其中一个成员必须初始化;
  • 如果类是类似于联合体但不是联合体的类,则对于其中具有变体成员的每个匿名联合成员,都必须初始化其中的一个;
  • 对于非委托构造函数,用于初始化非静态数据成员和基类子对象的每个所选构造函数都应是constexpr构造函数;
  • 对于委托构造函数,目标构造函数应为constexpr构造函数。

简而言之,只要满足上述其他要求,= default就是constexpr默认构造函数的有效定义。

那么这如何与未初始化的构造有关呢?

没有关系。

例如:

constexpr seconds x1{};

以上内容能够正常工作且将x1初始化为0s。然而:

constexpr seconds x2;

error: default initialization of an object of const type 'const seconds'
       (aka 'const duration<long long>') without a user-provided default
        constructor
    constexpr seconds x2;
                      ^
                        {}
1 error generated.

因此,要创建一个constexpr默认构造的duration,您必须对其进行零初始化。而= default实现允许使用{}进行零初始化。

完整的工作演示:

template <class Rep>
class my_duration
{
    Rep rep_;
public:
    constexpr my_duration() = default;
};


int
main()
{
    constexpr my_duration<int> x{};
}

有趣的侧边栏

在回答中我学到了一些东西,想和大家分享:

我一直在想为什么以下内容不起作用:

using Rep = int;

class my_duration
{
    Rep rep_;
public:
    constexpr my_duration() = default;
};


int
main()
{
    constexpr my_duration x{};
}

error: defaulted definition of default constructor is not constexpr
        constexpr my_duration() = default;
        ^
为什么将这个类改成非模板类会破坏constexpr默认构造函数?! (更新:在C++20中现在可以编译通过)。然后我尝试了这个:
using Rep = int;

class my_duration
{
    Rep rep_;
public:
    my_duration() = default;  // removed constexpr
};


int
main()
{
    constexpr my_duration x{};
}

编译器再次喜欢它。

CWG可能还没有关于此的问题,这个行为似乎有点不一致。这可能只是因为我们(整个行业)仍在学习constexpr。C++20中已修复。


但是在 constexpr 函数内部,你可以这样做 constexpr auto fun() { seconds x2; x2 = seconds{}; return x2; },对吗? - TemplateRex
嗯,确实。我记错了一些自己的代码,那些类有两步初始化,但这些类有在类内初始化器,并且构造函数上有=default,所以它们实际上是constexpr - TemplateRex
1
@BradSpencer 是的,对于非聚合体,S s{}值初始化,而 S s;默认初始化。如果 S 是一个带有默认构造函数的类,则在进行默认初始化之前,值初始化会将其零初始化。如果构造函数是 {} 而不是 = default,则这两种情况将是相同的(都会使 rep 未初始化)。参见 [dcl.init]/8。 - M.M
1
@BradSpencer:是的,基本上就是这样了。 "为什么"是因为标准这样规定。为什么标准这样规定?我不确定,我查看了http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2346.htm,但并没有立即看到有记录这个特性的原因。我喜欢它,因为它让客户端可以选择速度(不初始化)与零初始化。 - Howard Hinnant
1
@M.M:我不记得它是未定义的还是未指定的。但是,是的,你不能指望打印出什么值。 - Howard Hinnant
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接