如何最好地在 C++ 中实现 "newtype" 习语?
How best to implement the "newtype" idiom in C++?
自从学习 Rust,我就成了 newtype idiom which I gather Rust borrowed from Haskell 的粉丝。
新类型是基于标准类型的独特类型,可确保函数参数的类型正确。
例如,下面的 old_enough
函数必须传递一个以年为单位的年龄。它不会以天为单位或作为普通 i64 进行编译。
struct Days(i64);
struct Years(i64);
fn old_enough(age: &Years) -> bool {
age.0 >= 18
}
这与 C++ 中的 typedef
或 using
声明不同,后者只是重命名类型。
例如,下面的 old_enough
函数将接受 int
、Days
中的年龄或任何其他转换为 int
:
的内容
typedef int Days;
using Years = int;
bool old_enough(Years age) {
return age >= 18;
}
由于上面的例子只使用整数,Reddit 上的 this post 建议使用枚举 类,例如:
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 问题 类似。但是,它不考虑 newtype
习语。
What is the best way to implement the newtype
idiom in C++?
Rating the best 很多时候都在优先域中,但是您自己已经提到了两种替代方法:简单地自定义结构包装一个通用类型的值(比如int
),或使用 enum
classes 与明确指定的基础类型,用于强类型近乎相同的类型。
如果您主要追求常见类型的强类型别名,请说
struct Number { int value; }
或者,具有可参数化基础类型的通用类型
template<typename ValueType = int>
struct Number { ValueType value; }
然后 另一种 常用方法(这也有助于在强类型不同但相关类型之间重用功能)正在制作(/扩展)Number
class (template) class 模板参数化类型模板 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
class 模板的非成员函数(或例如将标签分派用于类似目的),您需要在别名声明之外声明类型标签。
我过去使用过 boost strong typedef。 documentation on it seems quite sparse, but fwiw, it seems to be used by facebook, and LLVM seems to have a similar thing called LLVM_YAML_STRONG_TYPEDEF
,表明它可能有一些真实世界的曝光。
如果你有 boost, BOOST_STRONG_TYPEDEF does exactly what you want as already seen in .
c++语言(目前)还没有任何东西可以直接如你所愿。但是话又说回来,详细的需求可能会有所不同,例如。有人可能会说可以进行隐式构造,而另一个人可能会说它必须是显式的。由于那个和其他组合1,很难提供一种机制来满足每个人,我们已经有了正常的类型别名(即。using
,这与 strong typedef).
不同
也就是说,c++ gives you enough tools that you can build this generic tool yourself and it is not completely difficult to do if you have some experience with templates,等等
最终取决于您实际遇到的 newtype 问题,例如。你只需要一把还是要批量制作这些。对于像 Years and Days 这样普通的东西,你可以只使用裸结构:
struct Days {int value;};
struct Years {int value;};
但是,如果您必须避免这样的情况:
bool isold(Years y);
...
isold({5});
然后您必须创建一个构造函数并将其显式化,即:
struct Years {
explicit Years(int i);
...
1 另一种组合可能是,例如,如果允许新类型转换为基础类型,可能对 int
之类的东西有用,或者它根据上下文可能很危险
自从学习 Rust,我就成了 newtype idiom which I gather Rust borrowed from Haskell 的粉丝。
新类型是基于标准类型的独特类型,可确保函数参数的类型正确。
例如,下面的 old_enough
函数必须传递一个以年为单位的年龄。它不会以天为单位或作为普通 i64 进行编译。
struct Days(i64);
struct Years(i64);
fn old_enough(age: &Years) -> bool {
age.0 >= 18
}
这与 C++ 中的 typedef
或 using
声明不同,后者只是重命名类型。
例如,下面的 old_enough
函数将接受 int
、Days
中的年龄或任何其他转换为 int
:
typedef int Days;
using Years = int;
bool old_enough(Years age) {
return age >= 18;
}
由于上面的例子只使用整数,Reddit 上的 this post 建议使用枚举 类,例如:
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 问题 newtype
习语。
What is the best way to implement the
newtype
idiom in C++?
Rating the best 很多时候都在优先域中,但是您自己已经提到了两种替代方法:简单地自定义结构包装一个通用类型的值(比如int
),或使用 enum
classes 与明确指定的基础类型,用于强类型近乎相同的类型。
如果您主要追求常见类型的强类型别名,请说
struct Number { int value; }
或者,具有可参数化基础类型的通用类型
template<typename ValueType = int>
struct Number { ValueType value; }
然后 另一种 常用方法(这也有助于在强类型不同但相关类型之间重用功能)正在制作(/扩展)Number
class (template) class 模板参数化类型模板 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
class 模板的非成员函数(或例如将标签分派用于类似目的),您需要在别名声明之外声明类型标签。
我过去使用过 boost strong typedef。 documentation on it seems quite sparse, but fwiw, it seems to be used by facebook, and LLVM seems to have a similar thing called LLVM_YAML_STRONG_TYPEDEF
,表明它可能有一些真实世界的曝光。
如果你有 boost, BOOST_STRONG_TYPEDEF does exactly what you want as already seen in
c++语言(目前)还没有任何东西可以直接如你所愿。但是话又说回来,详细的需求可能会有所不同,例如。有人可能会说可以进行隐式构造,而另一个人可能会说它必须是显式的。由于那个和其他组合1,很难提供一种机制来满足每个人,我们已经有了正常的类型别名(即。using
,这与 strong typedef).
也就是说,c++ gives you enough tools that you can build this generic tool yourself and it is not completely difficult to do if you have some experience with templates,等等
最终取决于您实际遇到的 newtype 问题,例如。你只需要一把还是要批量制作这些。对于像 Years and Days 这样普通的东西,你可以只使用裸结构:
struct Days {int value;};
struct Years {int value;};
但是,如果您必须避免这样的情况:
bool isold(Years y);
...
isold({5});
然后您必须创建一个构造函数并将其显式化,即:
struct Years {
explicit Years(int i);
...
1 另一种组合可能是,例如,如果允许新类型转换为基础类型,可能对 int
之类的东西有用,或者它根据上下文可能很危险