【问题标题】:How best to implement the "newtype" idiom in C++? [duplicate]如何最好地在 C++ 中实现“newtype”习语? [复制]
【发布时间】:2020-06-26 07:43:53
【问题描述】:

自从学习了 Rust,我就成为了 newtype idiom 的粉丝,我收集了 Rust 借用的 from Haskell

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

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

fn old_enough(age: &Years) -> bool {
    age.0 >= 18
}

这与 C++ 中的 typedefusing 声明不同,后者只是重命名类型。
例如,下面的 old_enough 函数将接受 intDays 中的年龄或任何其他转换为 int 的内容:

typedef int Days;
using Years = int;

bool old_enough(Years age) {
    return age >= 18;
}

由于上面的示例只使用整数,Reddit 上的this 帖子建议使用枚举类,例如:

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

bool old_enough(Years age) {
    return static_cast<int>(age) >= 18;
}

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

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

bool old_enough(Years age) {
    return age.value >= 18;
}

C++ 中实现newtype 成语的最佳方法是什么?
有标准方法吗?

EDIT 问题Strongly typed using and typedef 类似。但是,它不考虑newtype 成语。

【问题讨论】:

  • 在 C++ 中,您可能会创建一个 Year 类,并在构造函数和/或赋值级别进行验证。像auto year = new Year(2002) 然后year.old_enough(),或者对于更严格的实现,throw 当提供无效值时。
  • 如果你真的只需要相同的类型但作为不同的类型,不幸的是它需要相当多的样板。幸运的是已经有人写了,你可以使用BOOST_STRONG_TYPEDEF,但总的来说我同意tad,Year 不是int,应该有自己的类型
  • 这能回答你的问题吗? Strongly typed using and typedef
  • @tadman 你的意思是auto year = Year(2002);
  • 可能值得注意的是,对于这个特定的示例,C++ 标准库已经具有std::chrono::yearsstd::chrono::days,它们本质上已经是这种类型的包装器。 (尽管在这种情况下,也可能存在从 std::chrono::daysstd::chrono::seconds 的隐式转换,在幕后将值乘以正确的转换因子。)Boost 还有一个单位库,用于处理在 SI 中包装双精度值(或英制单位)并在幕后进行转换。

标签: c++ newtype


【解决方案1】:

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

评价最好很多次都在优先领域,但你自己已经提到了两种替代方法:简单的自定义结构包装一个常见类型的值(比如int),或使用具有显式指定基础类型的 enum 类用于强类型近乎相同的类型。

如果你主要是在强类型类型别名的普通类型之后,比如说

struct Number { int value; }

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

template<typename ValueType = int>
struct Number { ValueType value; }

然后另一种常用方法(这也有助于在强类型不同但相关的类型之间重用功能)正在制作(/扩展)Number 类(模板)参数化的类模板type 模板 tag 参数,这样标签类型的特化会导致强类型化。正如@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;
}

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

【讨论】:

  • 您可以更轻量级并避免 enum 利用“内联”结构声明:using YearNumber = Number&lt;struct YearTag&gt;;
  • @MatthieuM。太简洁了,我不知道您在模板参数列表中将结构声明为类型模板参数。谢谢,我会尽快更新答案。
  • @MatthieuM。我想这种方法的一个限制是如果你想例如根据其标签专门化 Number 类模板的一些非模板成员函数,和/或通过标签调度将一些非模板成员函数委托给具体的自定义(私有)成员函数。不过,如果出现该用例,它很容易适应。
  • @underscore_d Afaik anon。根据要求,结构总是在其声明处定义。 IIRC WG21/DR 62 谈到了这一点(“以下类型不得用作模板类型参数的模板参数:...未命名的类或枚举。类型没有用于链接目的的名称”)。但是,我无法从 [temp.arg.type] 找到 class-specifier 是有效的 template-argument,但这显然有效(对于我尝试过的所有编译器)。想法?
  • @underscore_d 因为我无法在 template-argument 中找到用于非类型模板参数 I posted a question about it 的标准覆盖(不完整)类声明,让更有经验的语言律师尝试一下。
【解决方案2】:

我过去使用过 boost strong typedef。它上面的documentation 看起来很稀疏,但fwiw,它似乎被facebook 使用,LLVM 似乎有一个similar thing called LLVM_YAML_STRONG_TYPEDEF,表明它可能有一些真实世界的曝光。

【讨论】:

    【解决方案3】:

    如果您有,BOOST_STRONG_TYPEDEF 会完全按照您在this answer 中看到的那样执行。


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

    话虽如此, 为您提供了足够的工具,您可以自己构建这个通用工具,如果您对 等有一些经验,这并不难做到。

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

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

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

    bool isold(Years y);
    
    ...
    
    isold({5});
    

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

    struct Years {
       explicit Years(int i);
    ...
    

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

    【讨论】:

      猜你喜欢
      • 2012-03-29
      • 2016-10-21
      • 1970-01-01
      • 2017-02-23
      • 2019-12-05
      • 1970-01-01
      • 1970-01-01
      • 2010-10-30
      • 1970-01-01
      相关资源
      最近更新 更多