在模板函数中自动将指针/迭代器转换为const

时间:2014-02-19 16:36:05

标签: c++ templates pointers iterator const

我正在尝试创建一个适用于指针和迭代器的函数(我希望在测试期间对集合进行迭代检查,并且可以灵活地使用数组来公开C库而不进行包装)。我想使用尽可能少的样板代码并且不依赖于Boost或C ++ 11功能来做到这一点。好吧,我没有,我的要求规范确实如此。

到目前为止,解决方案非常简单:在迭代器上参数化的函数模板。问题是我希望迭代器引用const值;部分是为了避免愚蠢的错误(改变一些严格意义上的输入值),部分是为了让编译器使用constness进行优化(这些值用于紧密循环中,欢迎每个优化。

考虑这个例子:

void foo(vector<int>::const_iterator it)
{
        //it[5] = 7; // 1
        int tmp = it[5];
}

void bar(const int *it)
{
        //it[5] = 7; // 2
        int tmp = it[5];
}

template<class T>
void baz(T it)
{
        it[5] = 7; // 3
        int tmp = it[5];
}

template<class T>
void qux(T it)
{
        //it[5] = 7; // 4
        int tmp = it[5];
}

template<class T>
void quux(const T it)
{
        it[5] = 7; // 5
        int tmp = it[5];
}

vector<int> a(10);
const vector<int> b(10);
int c[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
const int d[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

foo(a.begin());
foo(b.begin());
bar(c);
bar(d);
baz(a.begin());
//baz(b.begin()); // 5
baz(c);
//baz(d); // 6
qux(a.begin());
qux(b.begin());
qux(c);
qux(d);
quux(a.begin()); // 7
//quux(b.begin()); // 8
quux(c);
//quux(d); // 9

Foo和bar按预期工作;我可以读取值,如果我尝试回复第1行或第2行,我会收到编译错误。此外,我可以使用const和非const值调用函数。但这些都需要代码重复。

Baz使用简单的模板避免了这种情况。我可以使用指针和迭代器来调用它。但是,如果我从来没有用const值调用它(ll.5和6被注释掉),我可以分配给指向的值(l.3不会产生错误)。删除赋值(qux,l.4)允许我传递const和非成本值。原则上这很好(我可以使用测试用例来捕获错误),但它不会让编译器知道该值应该被视为const并相应地应用优化。在这种情况下,我更倾向于调用者不必担心constness(特别是因为忘记constness不是错误而且会“莫名其妙地”产生更慢的代码)。

现在,我想要一些像quux这样的东西,除了,你知道,工作。我想要的东西行为类似于foo和bar,但没有代码重复。问题是我得到一个const指针/迭代器到非const整数,而不是指针/迭代器(const或不是const)整数。我可以在第5行或(第8和第9行)但不是两者都离开。我希望编译器强制执行,如果我离开任何第7-9行,第5行是编译错误。

我知道,我可以使用模板特化和/或函数重载来解决这个问题(下面),但这需要我这样做(如果我忘记了我可能会在baz的情况下结束,除非没有测试用例找到错误)。代码应该易于移动,临时复制以用于替代实现等,但仍然可以实现高效和健壮。这也迫使我更多地了解我的收藏品。

template<class T>
void quux_(T it)
{
        it[5] = 7; // 5
        int tmp = it[5];
}

void quux(const int* it) { quux_<const int*>(it); }
void quux(std::vector<int>::const_iterator it) { quux_<std::vector<int>::const_iterator>(it); }

我也可以通过显式转换为相应的const值来解决这个问题(虽然如果你不知道这个集合,将转换和迭代器转换为相应的const_iterator似乎有点麻烦,而且C ++对它来说并不是很满意参数化集合,虽然我认为可以修复)。这确实强加了相当多的锅炉板(我有多达4个这样的参数和几十个这样的功能)。在这两个选择中,这是我的首选,但我喜欢foo和bar的简单性。

// template<class U> struct constify<typename std::vector<U>::iterator> { typedef typename std::vector<U>::const_iterator type; };
template<> struct constify<typename std::vector<int>::iterator> { typedef typename std::vector<int>::const_iterator type; };
template<> struct constify<std::vector<int>::const_iterator> { typedef std::vector<int>::const_iterator type; };
template<class U> struct constify<U*> { typedef const U* type; };
template<class U> struct constify<const U*> { typedef const U* type; };

template<class T>
void quux(T p_it)
{
        typename constify<T>::type it = p_it;
        //it[5] = 7; // 5
        int tmp = it[5];
}

有没有人有一种优雅的方法来实现这一目标?如果唯一的解决方案是显式地转换参数,我还想在更优雅的版本上输入 - 我很满意它是否只能处理向量迭代器和指针,但它最好在集合基类型中是通用的

1 个答案:

答案 0 :(得分:0)

遗憾的是,除了拥有模板专业化外,没有办法实现您的目标。

对我来说,一个干净的解决方案是:

//const version
template<class T>
void quux(const T it)
{ }

//non const version
template<class T>
void quux( T it)
{ }

正如我所看到的,这不是代码重复,两个接口是不同的,你应该只向客户提供他需要的最低限度。

当你尝试开始混合两者时,它只会导致问题。 如果您需要来修改函数内部的参数,只给出非const版本,客户端将能够使用const参数调用您而没有任何问题。 (非const到const cast开箱即用)

如果您可以确保不修改quux函数中的参数,只需提供const参数。

现在,const_casting在你的函数中修改它的参数只是一个大罪: - )

tldr; 想想你的界面暴露了什么,并编写最低限度的代码。