专门化 iterator_traits
我想将 std::iterator_traits<>
专门用于容器类模板的迭代器,该模板不具有通常的嵌套 typedef(例如 value_type) code>、
difference_type
等),我不应该修改其源代码。基本上我想做这样的事情:
template <typename T> struct iterator_traits<typename Container<T>::iterator>
{
typedef T value_type;
// etc.
};
除了这不起作用,因为编译器无法从 Container
推导出 T
。
有什么可行的方法可以达到同样的目的吗?
例如:
template <typename T>
class SomeContainerFromAThirdPartyLib
{
typedef T ValueType; // not value_type!
// no difference_type
class iterator
{
typedef T ValueType; // not value_type!
// no difference_type
...
};
iterator begin() { ... }
iterator end() { ... }
...
};
现在假设我使用此类的实例调用 std::count() 。据我所知,在大多数STL实现中,count()
返回iterator_traits
。 iterator_traits
的主要模板仅执行 typedef typename I::difference_type Difference_type
。与其他嵌套类型相同。
现在,在我们的示例中,这显然行不通,因为没有 Container::iterator::difference_type。我认为我可以通过将 iterator_traits
专门用于任何 Container
的迭代器来解决这个问题,而无需修改迭代器类。
最后,我只是希望能够使用 std 算法,如计数、查找、排序等,最好不修改任何现有代码。我认为 iterator_traits 的全部要点就是:能够为迭代器类型指定类型(如 value_type、diff_type 等),不支持它们内置。不幸的是,我无法弄清楚如何为 Container
的所有实例专门化特征类。
I'd like to specialize std::iterator_traits<>
for iterators of a container class template that does not have the usual nested typedefs (like value_type
, difference_type
, etc.) and whose source I shouldn't modify. Basically I'd like to do something like this:
template <typename T> struct iterator_traits<typename Container<T>::iterator>
{
typedef T value_type;
// etc.
};
except that this doesn't work, as the compiler is unable to deduce T
from Container<T>::iterator
.
Is there any working way to achieve the same?
For example:
template <typename T>
class SomeContainerFromAThirdPartyLib
{
typedef T ValueType; // not value_type!
// no difference_type
class iterator
{
typedef T ValueType; // not value_type!
// no difference_type
...
};
iterator begin() { ... }
iterator end() { ... }
...
};
Now suppose I call std::count()
using an instance of this class. As far as I know, in most STL implementations, count()
returns iterator_traits<Iterator>::difference_type
. The primary template of iterator_traits<I>
simply does typedef typename I::difference_type difference_type
. Same with the other nested types.
Now in our example this obviously won't work, as there's no Container::iterator::difference_type
. I thought I could work around this without modifying the iterator class, by specializing iterator_traits
for iterators of any Container<T>
.
In the end, I just want to be able to use std algorithms like count, find, sort, etc., preferably without modifying any existing code. I thought that the whole point of iterator_traits
is exactly that: being able to specify types (like value_type
, diff_type
etc.) for iterator types that do not support them built-in. Unfortunately I can't figure out how to specialize the traits class for all instances of Container<T>
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
是的。编译器无法从
Container::iterator
推断出T
,因为它是不可推断的上下文,换句话说,给定Container: :iterator
,T
的值不能唯一可靠地推导(参见详细说明)。此问题的唯一解决方案是,您必须完全专门针对您打算在程序中使用的
iterator
的每个可能值指定iterator_traits
。没有通用的解决方案,因为您不允许编辑Container
类模板。Yes. The compiler cannot deduce
T
fromContainer<T>::iterator
because it is non-deducible context, which in other words means, givenContainer<T>::iterator
, the value ofT
cannot uniquely and reliably be deduced (see this for detail explanation).The only solution to this problem is that you've to fully specialize
iterator_traits
for each possible value ofiterator
which you intend to use in your program. There is no generic solution, as you're not allowed to edit theContainer<T>
class template.Nawaz 的答案可能是大多数情况下的正确解决方案。但是,如果您尝试对许多实例化的
SomeContainerFromAThirdPartyLib
类和仅几个函数(或者未知数量的实例化但固定数量的函数,如您可能会发生的情况)执行此操作正在编写自己的库),还有另一种方法。假设我们得到以下(不可更改的)代码:
我们定义一个适配器类模板,其中包含
iterator_traits
所需的typedef
并对其进行专门化以避免指针问题:然后,对于我们希望能够使用
SomeContainerFromAThirdPartyLib::iterator
调用的每个函数,我们定义一个重载并使用 SFINAE:然后我们可以按如下方式使用它:
您可以找到一个可运行的示例以及 http://ideone 处所需的
include
和using
.com/gJyGxU。输出:不幸的是,有一些警告:
find
、sort
等)。这显然不适用于算法中尚未定义的函数。关于最后一个,问题是在哪个命名空间中放置重载(以及如何调用 std 版本)。理想情况下,它应该位于 ThirdPartyLib 中,以便可以通过参数相关的查找找到它,但我假设我们无法更改这一点。下一个最佳选择是在
MyLib
中,但调用必须经过using
限定或在其前面。在任何一种情况下,最终用户都应该使用using std::count;
或注意哪些调用需要使用std::
进行限定,因为 ifstd: :count
被错误地与SomeContainerFromAThirdPartyLib::iterator
一起使用,它显然会失败(这个练习的全部原因)。我不建议但出于完整性考虑而在此提出的另一种选择是将其直接放在
std
命名空间中。这会导致未定义的行为;虽然它可能对您有用,但标准中没有任何内容可以保证它。如果我们专门化count
而不是重载它,那么这将是合法的。Nawaz's answer is likely the right solution for most cases. However, if you're trying to do this for many instantiated
SomeContainerFromAThirdPartyLib<T>
classes and only a few functions (or an unknown number of instantiations but a fixed number of functions, as might happen if you're writing your own library), there's another way.Assume we're given the following (unchangeable) code:
We define an adapter class template containing the necessary
typedef
s foriterator_traits
and specialize it to avoid problems with pointers:Then, for each function we want to be able to call with a
SomeContainerFromAThirdPartyLib::iterator
, we define an overload and use SFINAE:We can then use it as follows:
You can find a runnable example with the required
include
s andusing
s at http://ideone.com/gJyGxU. The output:Unfortunately, there are caveats:
find
,sort
, et cetera). This obviously won't work for functions inalgorithm
that haven't been defined yet.In regards to that last one, the question is in which namespace to put the overload (and how to call the
std
version). Ideally, it would be inThirdPartyLib
so that it could be found by argument-dependant lookup, but I've assumed we can't change that. The next best option is inMyLib
, but then the call has to be qualified or preceded by ausing
. In either case the end-user should either useusing std::count;
or be careful about which calls to qualify withstd::
, since ifstd::count
is mistakenly used withSomeContainerFromAThirdPartyLib::iterator
, it will obviously fail (the whole reason for this exercise).An alternative that I do not suggest but present here for completeness would be to put it directly in the
std
namespace. This would cause undefined behavior; while it might work for you, there's nothing in the standard that guarantees it. If we were specializingcount
instead of overloading it, this would be legal.在所讨论的专业化中,
T
处于不可推导的上下文中,但既不需要第三方库容器代码更改,也不需要std
命名空间中的任何专业化。如果第三方库在各自的命名空间中没有提供任何免费的
begin
和end
函数,则可以编写自己的函数(如果需要启用 ADL,则可以写入该命名空间)并包装将迭代器放入自己的包装类中,该包装类又提供必要的类型定义和运算符。第一个需要迭代器包装器。
注意:也可以使
iterator_wrapper
继承Iterator
,或者使其更通用,并使用另一个帮助器来启用其他迭代器的包装.现在
begin()
和end()
:(也可以将它们放在与
SomeContainer
不同的命名空间中,但是如果有宽松的ADL。begin
和end
函数存在于该容器的命名空间中,我倾向于将适配器重命名为wbegin
和wend
。)现在可以使用这些函数调用标准算法:
如果
begin()
和end()
包含在库命名空间中,容器甚至可以用于更通用的上下文中。此类代码可以与
std::vector
以及ThirdPartyLib::SomeContainer
一起使用,只要 ADL 找到begin()
和>end()
返回包装迭代器。In the specialization in question,
T
is in a nondeducible context but there is neither a third party library container code change nor any specialization in thestd
namespace required.If the third party library does not provide any free
begin
andend
functions in the respective namespace one can write own functions (into that namespace if desired to enable ADL) and wrap the iterator into an own wrapper class which in turn provides the necessary typedefs and operators.First one needs the Iterator wrapper.
Note: It would also be possible to make
iterator_wrapper
inherit fromIterator
, or to make it more generic and have another helper to enable the wrapping of other iterators as well.Now
begin()
andend()
:(It is also possible to have them in a different namespace than
SomeContainer
but loose ADL. IF there arebegin
andend
functions present in the namespace for that container I'd tend to rename the adaptors to be something likewbegin
andwend
.)The standard algorithms can be called using those functions now:
If
begin()
andend()
are included into the library namespace, the container can even be used in more generic contexts.Such code can be used with
std::vector
as well asThirdPartyLib::SomeContainer
, as long as ADL findsbegin()
andend()
returning the wrapper iterator.您可以很好地使用
Container
作为iterator_traits
的模板参数。对于 STL 的其余部分来说,重要的是特征类中的 typedef,例如value_type
。这些应该正确设置:然后您将在之前使用
T
的地方使用value_type
。至于使用特征类,您当然可以使用外部容器的类型对其进行参数化:
当然,这假设
TheContainer
符合常见的 STL 容器的约定并具有value_type
定义正确。You can very well use the
Container
as template parameter to youriterator_traits
. What matters to the rest of STL are the typedefs inside your traits class, such asvalue_type
. Those should be set correctly:You would then use
value_type
where you would previously useT
.As for using the traits class, you of course parametrize it with the type of your external container:
Naturally, this assumes
TheContainer
is conforms to the common STL containers' contract and hasvalue_type
defined correctly.