【问题标题】:Detect compile-time literals and constants检测编译时文字和常量
【发布时间】:2018-11-13 00:24:23
【问题描述】:

假设我想编写一个通用类来维护一个始终保持在两个值之间的整数。像这样的:

template<int Lower, int Upper>
class MyInt {
    private:
        int m_int;
    public:
        // Constructors, operators...
};

类不变量是Lower &lt;= m_int &lt;= Upper。 当然 MyInt 应该具有整数倾向于具有的所有常用操作,例如赋值和算术运算符。如果一个操作要让它处于破坏其不变量的状态,MyInt 会抛出。然而,在许多情况下,这应该是编译时可检测到的。考虑这个示例代码:

int foo = 500;
constexpr int const bar = 500;
MyInt<0,100> a = 15; // OK
MyInt<0,100> b = foo; // Throws at runtime
MyInt<0,100> c = 500; // Compile error?
MyInt<0,100> d = bar; // Compile error?
MyInt<0,100> f = std::integral_constant<int, 500>; // Compile error

对于std::integral_constant,编写适当的构造函数很简单。但是是否有可能编译时检测到a 在范围内而cd 不在范围内?分配的值要么是编译时已知的文字,要么是 constexpr 常量。

我已经尝试过 SFINAE-ing 等等,但我找不到从值语义到模板参数的方法,即使对于这些(我声称)值显然是编译时常量的情况。

对于 C++17,该语言不提供实现此功能所需的工具是否属实?如果是,这是否会随着即将到来的 C++20 而改变?如果我正在寻找的东西是不可能的,这是什么原因?我的兴趣完全是教育性的,所以如果我遗漏了一些东西(比如文字实际上不是编译时常量或其他东西),我会很感兴趣。

注意:我知道 f 的大小写可以通过引入自定义文字后缀并要求用户键入以下内容来变得不那么难看:

MyInt<0,100> g = 15_constint; // OK
MyInt<0,100> h = 500_constint; // Compile error

我也知道提供这样的界面的可能性:

MyInt<0,100> i;
i.assign<500>(); // Compile error

但是,这两种变通方法都有一定的输入开销,并且不一定是惯用的 C++。

【问题讨论】:

  • "...如果我遗漏了一些东西(比如文字实际上不是编译时常量或其他东西)" - 令人惊讶的是,该语言的脑死亡程度次。就好像 C++ 委员会假装源文件一旦保存到磁盘就会以某种方式发生变化,这违反了我们所理解的所有物理定律......
  • @jww 好吧,即使基于相同的源文件,编译单元在编译时也可能变成许多不同的东西,这取决于预处理器宏的值:-/
  • 这会随着即将到来的 C++20 而改变吗?” C++20 还没有完成,所以询问将会或不会出现什么这有点投机。

标签: c++ templates c++17 generic-programming c++20


【解决方案1】:

如果我正在寻找的东西是不可能的,这是什么原因?

基本上归结为这样一个事实,即 C++ 函数模型不允许函数识别 constexpr 参数和运行时参数之间的区别。如果一个函数采用int,那么它采用int。该函数的行为不会因int 是由常量表达式(如文字)还是运行时值提供而有所不同。

这对于constexpr 函数来说也是如此。您不能拥有一个可以对其参数进行编译时检查的constexpr 函数。评估可以在编译时完成,但执行 foo(500); 最终的行为必须与执行 auto b = 500; foo(b); 相同。

这就是为什么解决方法都涉及使用技巧将常量表达式放入模板参数中,这样可以识别。当然,模板参数从不是运行时值,因此会产生其他问题(例如必须具有多个函数)。作为一个模板参数通常是一件痛苦的事情。

基本上,您想要的要求函数可以声明参数为constexpr(以及允许您拥有非constexpr 版本的重载规则)。没有这样的提案被投票通过 C++20。已经做过的最好的是P1045,它甚至还没有被委员会讨论过,并且有一堆漏洞需要填补。所以我不会屏住呼吸。

【讨论】:

  • 我不同意constexpr 函数不能进行编译时检查。看看我的回答和更一般的啊 throw in constexpr 函数的含义
  • @bartop:我说“这是广泛真的”。
  • if constexpr 我会说是“编译时检查”。
  • @JesperJuhl:不能在parameters上使用,因为函数参数从来都不是常量表达式。
【解决方案2】:

试试这个。这是一个非常简单可扩展的示例:

template<int Max, int Min>
class MyInt
{
public:
    constexpr MyInt(int i)
        : m_int(i > Max
            ? throw std::exception("out of bounds int")
            : ( i < Min
                ? throw std::exception("out of bounds int")
                : i))
    {}

private:
    int m_int;
};


int main()
{
    MyInt<1, 2> i(4);
}

它显示编译时错误:)。我使用这个 SO 问题作为指导: constexpr constructor with compile time validation

【讨论】:

  • 这是一个巧妙的技巧,但它只有在你将变量设为 constexpr 时才有效,即 constexpr MyInt&lt;1, 2&gt; i (4); 但我正在寻找的是我不必是 constexpr 的东西;毕竟,4 在编译时是已知的。
  • @Drag-On:为了进行编译时测试,您需要编译时评估。在这种情况下,评估i的初始化。在 C++ 中拼写为 constexpr。非constexpr 变量可能会在编译时初始化,但这取决于编译器。仅对constexpr 变量保证
  • @Drag-On 你总是可以使这个类的 constexpr 工厂友元函数,并使构造函数私有。这将在调用该方法时强制执行检查。但是当然需要打电话而不是分配可能会很烦人。另一个肮脏的解决方案是预处理器的使用
  • 请注意,对于绑定错误,您可以使用std::range_error 而不是默认的std::exception
猜你喜欢
  • 2011-03-19
  • 2014-09-03
  • 2012-02-23
  • 2014-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多