有没有一种方法可以模拟"strongdef"?

13

你可能已经知道,在C++中,typedef更像是一个别名而不是新类型,详情可以参见这里:http://dlang.org/cpptod.html#typedefs
我真的不喜欢链接中提出的解决方案,所以我想知道是否有更好的方法?


1
我必须承认,C/C++在类型方面存在很大问题。其他一些语言,如Ada,在这方面有更好的方案。即使两者都由整数表示,您也不应该能够将3个胡萝卜加上3个土豆。 - Alexis Wilke
5个回答

7
只有一种方法在C++中引入新类型——使用类关键字(union/struct/class/enum...)。有办法通过宏来隐藏这些内容,并使这些新类型像旧类型一样直观易用(参见BOOST_STRONG_TYPEDEF),但事实仍然是,这是引入新类型的唯一方法。
想象一下,如果你有一个newtype关键字,可以创建一个强类型定义:
newtype foo = int; // with 'using' alias syntax

如何进行该类型的转换?如果没有转换,您将无法为新类型的对象分配任何值。只有显式转换可能看起来直观,但是如果您确实希望隐式转换,并且仍然能够进行重载,那么就很遗憾了。您可能能够添加各种语法以让用户做决定,但我相信您总是可以想出需要新语法的极端情况。只需将其作为“struct”,在宏后面隐藏它并完成。

1
第二段描述了一个已解决的问题。例如,您可以使用语法newtype foo = narrowing<int>以及widening<>distinct<>explicit<>和更多组合。其他编程语言实际上也具有这样的功能。 - Konrad Rudolph
1
@Konrad: 那么,如果你已经选择使用类似模板的特殊东西来指定转换,为什么不直接使用 using foo = narrowing<int, struct _foo_int_key> 呢?(这基本上就是 BOOST_STRONG_TYPEDEF 在幕后执行的操作)。由于这可以利用语言本身完成,所以我认为将其集成到核心语言中没有太大的必要。 - Xeo
1
无法完成。Boost仅适用于原始类型,我确信没有任何自动为具有成员函数的任何类型执行此操作的方法。您可以提升运算符-原则上,您不能提升成员函数。 - Konrad Rudolph
哦,BOOST_STRONG_TYPEDEF真的只适用于内置类型吗?我忽略了那部分。让我编辑一下。 - Xeo
如何访问底层类型,例如 BOOST_STRONG_TYPEDEF(std::string, name); name n; 如何对 n 进行 .resize() 操作? - NoSenseEtAl
显示剩余3条评论

2

通用的“没时间做一个合适接口”的宏:

#define STRONG_CLASS_TYPEDEF(oldType, newType) \
struct newType : private oldType {             \
    template<typename... T>                    \
    newType(T... foo) : oldType(foo...) {}     \
    oldType* operator->() { return this; }     \
    oldType& operator*() {return *this;}       \
};

如果您的编译器不支持可变参数模板,则需要手动插入所需的任何构造函数。这基本上是一个自己构建继承构造函数的过程。
不幸的是,我认为不可能隐式限制新类型向基类转换,但暴露其所有公共字段。有一些解决方法:
您可以使用 using oldType::X; 来使用您需要的所有方法和变量。这绝对是最好的解决方案,但需要花费一些时间。
或者使用狡猾的箭头运算符重载并调用 foo->method();。或者将新的强类型“取消引用”为基础类型。这些基本上只是一种花哨的显式转换。但考虑到 operator oldType()(是否显式)甚至不能与私有继承一起使用...
无论如何,这里是一个针对 std::stringName 示例的强类型。
struct Name : private std::string {
    template<typename... T>
    Name(T... foo) : std::string(foo...) {}

    std::string* operator->() { return this; }
    const std::string& operator*() {return *this;}
    //The above is obviously a bad idea if you want to use this alongside pointers

    using std::string::resize;
    using std::string::size;
    using std::string::insert;
    using std::string::operator[];
    // etc.
    //Simplest to use, but a bit more to set up
};

此外,您需要包装非成员运算符重载(例如此示例中的std :: string == )。
bool operator==(Name& lhs, Name& rhs) { return *lhs == *rhs; }
//Note: "Dereference" turns it into a std::string, as above

提醒一下,我没有测试过基本机制以外的内容。这可能会增加(非常)少量的开销。但是,这个几乎为空的类可能已经被优化掉了。


使用大量的using会破坏强类型定义的目的,因为它应该是简单的。你可以向基类添加公共引用,但是对于像operator==这样的函数,该怎么办呢? - BЈовић
我在底部的答案中解决了operator==的问题。是的,using相当冗长。但它并没有违背其目的,因为它仍然不允许转换为基类。我不确定您所说的“对基类的公共引用”是什么意思。如果您指的是公共继承,则这违背了typedef的目的,因为它允许隐式转换为基类。 - ECrownofFire
像这样:struct Name : private std::string { std::string& t; }; - BЈовић
啊,我明白你的意思了。假设你只是让它引用自身,那么这很容易实现。这也比重载要干净得多。 - ECrownofFire
我认为这是一种实现BOOST_STRONG_TYPEDEF的方式。 - BЈовић

1

在C++中使用BOOST_STRONG_TYPEDEF创建“strongdefs”。没有内置的语言功能可以实现这一点。

话虽如此,了解您尝试解决的真正问题会很有趣,因为我发现在我所涉及的代码中,需要非别名typedef的情况非常少。


这样做可以让人们难以出错 :) 比如,将传递给它们的第一、第二和第五个字符串转发到另一个函数中的函数。 :D - NoSenseEtAl

0

C++ 中有许多方法可以执行类型检查。 考虑以下代码,其基本思想与链接中给出的示例相同,但我认为它更直观:

struct Handle2 {void * ptr;} ;
void bar(Handle2) {}
void foo(void *) {}

int main() {
    Handle2 h2 = {(void*)-1};
    foo(h2); //! won't pass through compiling
    bar(h2);
}

0

C++中的新类型只能通过聚合子类型来实现。但由于函数重载是基于静态类型的,因此所有操作和函数都必须重新声明。

C++中的typedef类似于D语言中的alias

如果起始类型是类或结构体,则继承可以在一定程度上帮助(至少操作仍然可以像旧参数一样工作),但一切都关乎返回类型和转换必须适当地重新定义,否则一切都会随意地向上和向下转换,从而消失了新类型的优势。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接