如何最好地在 C++ 中实现“newtype”习语?

ken*_*nba 31 c++ newtype

自从学习 Rust 以来,我已经成为我收集 Rust从 Haskell借来的新型习语的粉丝。

新类型是基于标准类型的独特类型,可确保函数参数的类型正确。
例如,old_enough下面的函数必须以年为单位传递年龄。它不会以天数或普通 i64 为单位编译。

struct Days(i64);
struct Years(i64);

fn old_enough(age: &Years) -> bool {
    age.0 >= 18
}
Run Code Online (Sandbox Code Playgroud)

这与C++ 中的typedeforusing声明不同,它只是重命名类型。
例如,old_enough下面的函数将接受 an int、age inDays或任何其他转换为 an 的内容int

typedef int Days;
using Years = int;

bool old_enough(Years age) {
    return age >= 18;
}
Run Code Online (Sandbox Code Playgroud)

由于上面的示例仅使用整数,因此Reddit 上的这篇文章建议使用枚举类,例如:

enum class Days : int {};
enum class Years : int {};

bool old_enough(Years age) {
    return static_cast<int>(age) >= 18;
}
Run Code Online (Sandbox Code Playgroud)

或者它可以简单地使用结构,例如 Rust:

struct Days final {int value;};
struct Years final {int value;};

bool old_enough(Years age) {
    return age.value >= 18;
}
Run Code Online (Sandbox Code Playgroud)

在 中实施newtype习语的最佳方法是什么C++
有标准的方法吗?

编辑问题Strongly typed using 和 typedef是相似的。但是,它不考虑newtype习语。

dfr*_*fri 14

newtype在 C++ 中实现习语的最佳方法是什么?

多次评级最好最终在优先域中,但您自己已经提到了两种替代方法:简单地自定义结构包装一个公共类型的值(例如int),或者使用enum具有显式指定底层类型的类作为近- 相同的类型。

如果你主要是在一个常见类型的强类型类型别名之后,说

struct Number { int value; }
Run Code Online (Sandbox Code Playgroud)

或者,具有可参数化基础类型的通用类型

template<typename ValueType = int>
struct Number { ValueType value; }
Run Code Online (Sandbox Code Playgroud)

然后另一种常见的方法(这也有助于在强类型不同但相关的类型之间重用功能)是使(/扩展)Number类(模板)成为一个通过类型模板标签参数参数化的类模板,这样标签类型的特化结果在强类型中。正如@Matthieu M. 所指出的,我们可以将结构声明为给定特化的模板参数列表的一部分,允许在单个别名声明中进行轻量级标记声明和别名标记:

template<typename Tag, typename ValueType = int>
struct Number {
    ValueType value;
    // ... common number functionality.
};

using YearNumber = Number<struct NumberTag>;
using DayNumber = Number<struct DayTag>;

void takeYears(const YearNumber&) {}
void takeDays(const DayNumber&) {}

int main() {
    YearNumber y{2020};
    DayNumber d{5};
    
    takeYears(y);
    //takeDays(y);  // error: candidate function not viable
    
    takeDays(d);
    //takeYears(d);  // error: candidate function not viable
    
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果您想Number为特定标签专门化类模板的非成员函数(或例如出于类似目的使用标签调度),您需要在别名声明之外声明类型标签。

  • 您可以更加轻量级,并避免使用“内联”结构声明的“枚举”:“using YearNumber = Number&lt;struct YearTag&gt;;” (3认同)

dar*_*une 2

如果您有本答案中所示。


C++ 语言中还没有任何语言可以直接按照您的意愿完成此操作。但话又说回来,详细的需求可能会有所不同,例如。有人可能会说可以进行隐式构造,而另一个人可能会说它必须是显式的。由于这种情况和其他组合1,很难提供一种能够满足所有人的机制,并且我们已经有了正常的类型别名(即,using其中 ofc. 与强 typedef不同)。

话虽如此,等方面的经验,那么这并不完全困难。

最后,这取决于您实际遇到的新型问题,例如。你只需要一把还是要批量生产。对于像年和日这样的普通东西,你可以只使用裸结构:

struct Days {int value;};

struct Years {int value;};
Run Code Online (Sandbox Code Playgroud)

但是,如果您必须避免这样的情况:

bool isold(Years y);

...

isold({5});
Run Code Online (Sandbox Code Playgroud)

然后,您必须创建一个构造函数并使其显式,即:

struct Years {
   explicit Years(int i);
...
Run Code Online (Sandbox Code Playgroud)

1另一种组合可能是,如果应允许新类型转换为基础类型,对于类似的东西可能有用int,或者根据上下文可能是危险的