检测编译时文字和常量
Detect compile-time literals and constants
假设我想编写一个泛型 class 来维护一个始终介于两个值之间的整数。像这样:
template<int Lower, int Upper>
class MyInt {
private:
int m_int;
public:
// Constructors, operators...
};
class 不变式是 Lower <= m_int <= 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
在范围内而 c
和 d
不在范围内?分配的值是编译时已知的文字或 constexpr 常量。
我试过 SFINAE 等等,但我找不到从值语义到模板参数的方法,即使对于(我声称)这些值显然是编译时常量的情况也是如此。
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++。
In case what I'm looking for is impossible, what are the reasons for this?
基本上归结为 C++ 函数模型不允许函数识别 constexpr
参数和运行时参数之间的区别。如果函数采用 int
,则它采用 int
。根据 int
是由常量表达式(如文字)还是运行时值提供,该函数的行为不会有所不同。
即使对于 constexpr
函数 也是如此。您不能拥有可以对其参数进行编译时检查的 constexpr
函数。评估可以在编译时完成,但是 foo(500);
的最终行为必须与 auto b = 500; foo(b);
.
相同
这就是为什么解决方法都涉及使用技巧将常量表达式放在模板参数中,这样的识别是可能的。当然,模板参数可以 永远不会 成为运行时值,因此会产生其他问题(例如必须具有多个函数)。作为模板参数通常是一件痛苦的事情。
本质上,您想要的是函数可以声明参数为 constexpr
(以及允许您拥有非 constexpr
版本的重载规则)。没有这样的提议被投票到 C++20 中。做的最好的是P1045,连组委会都没有讨论过,还有一堆坑要补,所以我就不憋了。
试试这个。这是一个非常简单的可扩展示例:
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
假设我想编写一个泛型 class 来维护一个始终介于两个值之间的整数。像这样:
template<int Lower, int Upper>
class MyInt {
private:
int m_int;
public:
// Constructors, operators...
};
class 不变式是 Lower <= m_int <= 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
在范围内而 c
和 d
不在范围内?分配的值是编译时已知的文字或 constexpr 常量。
我试过 SFINAE 等等,但我找不到从值语义到模板参数的方法,即使对于(我声称)这些值显然是编译时常量的情况也是如此。
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++。
In case what I'm looking for is impossible, what are the reasons for this?
基本上归结为 C++ 函数模型不允许函数识别 constexpr
参数和运行时参数之间的区别。如果函数采用 int
,则它采用 int
。根据 int
是由常量表达式(如文字)还是运行时值提供,该函数的行为不会有所不同。
即使对于 constexpr
函数 也是如此。您不能拥有可以对其参数进行编译时检查的 constexpr
函数。评估可以在编译时完成,但是 foo(500);
的最终行为必须与 auto b = 500; foo(b);
.
这就是为什么解决方法都涉及使用技巧将常量表达式放在模板参数中,这样的识别是可能的。当然,模板参数可以 永远不会 成为运行时值,因此会产生其他问题(例如必须具有多个函数)。作为模板参数通常是一件痛苦的事情。
本质上,您想要的是函数可以声明参数为 constexpr
(以及允许您拥有非 constexpr
版本的重载规则)。没有这样的提议被投票到 C++20 中。做的最好的是P1045,连组委会都没有讨论过,还有一堆坑要补,所以我就不憋了。
试试这个。这是一个非常简单的可扩展示例:
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