在结构体中包装数字以避免隐式转换的利弊



我在写一个小库,代码如下:

using foo_t = int;
using bar_t = int;
baz_t create_baz(foo_t foo, bar_t bar = default_bar);

我还想要一个create_baz函数它只需要一个bar

baz_t create_baz(bar_t bar);

,但这将与默认的第一个版本create_baz冲突-因为foo_tbar_t是相同的。所以,我在想:也许我应该把

struct foo_t { int value; }
struct bar_t { int value; }

(或者只是将它们中的一个包装在结构体中),这样类型就会不同,甚至不兼容,并且我将拥有第二个create_baz

现在,显然结构体使用起来不太方便。但是,在做这个决定时,还有其他的利弊需要我考虑吗?

我的问题主要是关于c++的,但我想这在C中也有一定的相关性(忽略第一个函数的第二个参数)。

以下不是您确切问题的答案,但BOOST_STRONG_TYPEDEF似乎可以很好地创建不兼容的类型:

#include <boost/strong_typedef.hpp>
// Only the header is required. No linking, no runtime dependencies.
BOOST_STRONG_TYPEDEF(int, foo_t);
BOOST_STRONG_TYPEDEF(int, bar_t);
void create_baz(foo_t foo, bar_t bar = static_cast<bar_t>(1)) {}
void create_baz(bar_t bar) {}
int main()
{
    // Assigning (or initializing with) literals seems to be 
    // the only inconvenient thing about strong typedefs.
    foo_t a = static_cast<foo_t>(1);
    foo_t b = static_cast<foo_t>(2);
    a = b; // Operators work well.
    std::cout << a+b << std::endl; // Even overloaded ones.
    create_baz(a); // No errors, no ambiguity.
    return 0;
}

消除函数调用歧义的常见、基本和明显的技术是添加额外的形参并依赖重载。
它是如此常见,甚至标准模板库中的一些类也使用它。

作为一个例子,考虑std::allocator_arg_t的目的:

[…一个空类类型,用于消除分配器感知对象的构造函数和成员函数的重载歧义[…]

std::functionstd::tuplestd::promisestd::packaged_task使用

您可以很容易地查看这些类的构造函数列表,以了解它是如何使用的。

另一个例子是后置自增/自减操作符。它有一个 int参数,有助于区分函数声明和前缀自增/自减操作符。
即:

T& operator++(T&)

T& operator++(T&, int)

这里的调用不需要你提供额外的参数,它实际上只是用来消除函数声明的歧义,所以它是一个人为的例子。

不管怎样,在你的情况下你能做些什么呢?
你可以像这样定义一个标签:
struct bar_arg_t {};

并在第二个函数声明中使用它:

baz_t create_baz(bar_arg_t, bar_t bar);

这只不过是一个空类,一个虚拟参数,其目的是帮助您消除调用的歧义。

最新更新