有时对于代数类型,即使底层类型不是整数,也可以使用一个构造函数来表示中性元素,或者表示乘法单位元素。
问题是,如何说服编译器只接受0
或1
而不接受任何 * 其他 * 整数并不明显。
在C++14或更高版本中有没有方法可以做到这一点,例如组合文字、constexpr或static_assert?
让我用一个自由函数来说明(尽管我们的想法是将该技术用于接受单个参数的构造函数。构造函数也不能接受模板参数)。
一个只接受零的函数可以写成这样:
constexpr void f_zero(int zero){assert(zero==0); ...}
问题是,这只可能在运行时失败。我可以写f_zero(2)
甚至f_zero(2.2)
,程序仍然会编译。
第二种情况很容易删除,例如使用enable_if
template<class Int, typename = std::enable_if_t<std::is_same<Int, int>{}> >
constexpr void g_zero(Int zero){assert(zero==0);}
这仍然有一个问题,我可以传递任何整数(而且它只在调试模式下失败)。
在C++ pre11中,人们有能力做到这个技巧,只接受一个零。
struct zero_tag_{};
using zero_t = zero_tag_***;
constexpr void h_zero(zero_t zero){assert(zero==nullptr);}
这实际上允许99%的人在那里,除了非常丑陋的错误消息。因为,基本上(模Maquevelian使用),唯一接受的参数将是h_zero(0)
。
这就是https://godbolt.org/z/wSD9ri的情况,我看到Boost.Units库中使用了这种技术。
1)现在使用C++的新功能可以做得更好吗?
我问这个问题的原因是因为对于字面量1
,上述技术完全失败。
2)是否有一个等效的技巧可以应用于字面量1
的情况?(理想情况下作为一个单独的函数)。
我可以想象我们可以发明一个非标准的long long literal _c
来创建std::integral_constant<int, 0>
或std::integral_constant<int, 1>
的示例,然后让函数采用这些类型,但是对于0
的情况,最终的语法会是最糟糕的,也许有更简单的方法。
f(0_c);
f(1_c);
编辑:我应该提到,由于f(0)
和f(1)
可能是完全独立的函数,因此理想情况下它们应该调用不同的函数(或重载)。
8条答案
按热度按时间unftdfkk1#
在C++20中,你可以使用
consteval
关键字来强制编译时求值。通过它你可以创建一个结构体,它有一个consteval
构造函数,并将其用作函数的参数。这里还有一个godbolt example。
编辑:
看起来
clang 10
没有像here那样给出错误,但是godbolt上的clang (trunk)
给出了错误。wyyhbhjk2#
可以通过将0或1作为模板参数传递来实现,如下所示:
函数的调用方式如下:我不相信同样的事情可以对构造函数做(因为你不能显式地为构造函数设置模板参数),但是你可以使构造函数私有化,并让静态 Package 函数可以被赋予模板参数来执行检查:
A
类型的对象将使用A::make_A<0>()
创建。kadbb4593#
那么...你已经标记了C++17,所以你可以使用
if constexpr
。因此,当
0_x
是std::integral_constant<int, 0>
值、1_x
是std::integral_constant<int, 1>
以及2_x
(和其他值)出现编译错误时,您可以定义一个文本类型。举例
现在
f()
函数可以是你可以这样称呼它
p5cysglq4#
对于Ada,可以定义子类型、新类型或仅对Integer 0和1的值进行约束的派生类型。
编译时,尽管编译成功,但仍会收到以下警告消息:
执行它会产生编译器指定的运行时错误
所以,基本上你可以通过定义新的类型,派生类型或子类型来约束值,你不需要包含代码来检查范围,但是基于你的数据类型,编译器会自动警告你。
wribegjk5#
这不是一个现代的解决方案,但是添加到Zach Peltzer的解决方案中,如果您使用宏,您可以保持您的语法...
尽管如此,对于构造函数问题,您可以将类模板化,而不是尝试使用模板化的构造函数
pxiryf3j6#
迄今为止,我发现接受字面量
0
的最佳解决方案是使用std::nullptr_t
作为函数的输入:与其他一些解决方案相比,这具有转换优势。例如,它允许使用..
void MyFunc(const math_object &obj=0);
这样的语法。我已经使用它很多年了,没有发现任何问题。但是,我没有类似的解决方案来处理文字1
。为此,我创建了一个具有全局IDENTITY
变量的construct::id
结构。inkz8wg97#
这是一个基本的问题,在编译器中,如何对一个参数进行编译,同时又是有效的,那么,你到底需要什么呢?
这包含在Pascal或Ada等强类型语言中。枚举类型只有两个值,通常在开发时检查类型,但在运行时,某些编译器选项会取消检查,因为 * 一切顺利 *。
函数接口是一个契约。它是卖方与卖方之间的契约(函数的编写者)和买方(该函数的用户)。甚至有一个仲裁器,这是一种编程语言,如果有人试图欺骗合同,它可以采取行动。但最终,程序运行在一台机器上,这台机器可以随意修改枚举值集,并完全放置在(和不允许的值)。
这个问题也伴随着单独编译而来。单独编译有它的缺点,因为它必须面对一个编译,而不需要重新检查和重新测试你之前做过的所有编译。一旦编译完成,你放入代码中的所有东西都在那里。如果你想让代码高效,那么测试就是多余的,因为调用者和实现者都要科普契约,但是如果你想捕捉一个谎言,那么你必须包含测试代码。然后,最好对所有情况都做一次,或者最好让程序员决定我们何时想捕捉一个谎言?
C语言的问题他们的灵感来自于优秀的程序员,他们不会出错,他们的软件必须在又大又慢的机器上运行。他们决定同时开发两种语言(第二个是为了互操作性)弱类型......它们确实如此。你试过用Ada?或Modula-2编程吗?随着时间的推移,你会发现,强打字的东西比其他东西更学术,最后你想要的,作为一个专业人士,是有自由说:现在我希望安全(并包含测试代码),现在我知道我在做什么(请尽可能高效)
结论
结论是,你可以自由选择语言、选择编译器、放松规则,编译器有可能允许你这样做,而你必须科普它,或者发明(这是今天几乎每周都会发生的事情)你自己的编程语言。
bfrts1fy8#
这是我的问题的答案,基于@IlCapitano对一个 Package 类的回答。这个 Package 类可以私有化,并且只在构造时使用。
这样,只允许
Matrix A(0)
或Matrix A(1)
。(虽然它也适用于常量变量,但这是可以的。)这里显示了构造函数中的“运行时”
if
不是问题,可以由编译器省略:https://godbolt.org/z/hd6TWY6qW该解决方案需要C++20和GCC和clang的最新版本中工作。