如何断言模板参数的类型 STL 迭代器类型



假设我想编写通用函数来打印出集合中的标准输出范围。由于它应该是通用的,因此我认为...

std::vector<std::string> names = { "John", "Henry", "Mark" };

以及:

std::vector<int> years = { 100, 200, 400 };

..将有可能打印出来。

由于集合的类型可能不同,并且 STL 集合没有基类,让我有机会传递基类迭代器,因此我使用模板函数:

template<typename TIterator>
void PrintRange( TIterator beginIter,TIterator endIter )
{           
for( auto it = beginIter; it != endIter; ++it )
{
std::cout << *it << std::endl;
}
}

现在一切正常,现在我可以写:

PrintRange( names.begin(), names.end() );

和:

PrintRange( years.begin(), years.end() );

但是现在我想帮助我的函数的客户更快地理解为什么他在使用它时会出现错误。现在当我打电话时:

PrintRange( 100, 400 );

有错误:

main.cpp:23:34:错误:一元"*"的类型参数无效(具有"int")

我想打印类似的东西:

其中一个参数与类型的预期参数不对应 "迭代器">

那么解决这个问题的方法最好:

  1. 关心错误消息是否为 正如我所料有意义。用户应分析模板类代码以 确定他错误的原因。

  2. 使用static_assert来断言所有已知的可能性......但是如何断言函数的参数是任何迭代器,因为没有基类?

static_assert( std::is_base_of::迭代器>::value );

这只会断言字符串迭代器的向量...

就个人而言,我认为您的第一种方法完全没问题,因此您可能不太关心其他错误消息。

另一方面,如果您决定打印有意义的消息,则可以实现自定义类型特征来检测迭代器,如此处所述,然后将其与static_assert一起使用。因此,代码转换为类似以下内容:

template<typename TIterator>
void PrintRange(TIterator beginIter, TIterator endIter)
{        
static_assert(is_iterator<TIterator>::value,
"TIterator is not an iterator type");
for( auto it = beginIter; it != endIter; ++it )
{
std::cout << *it << std::endl;
}
}

Edgar Rokyan 提供的答案非常有帮助,但我知道另一种解决方案(可能更糟糕,因为我们必须实现更多的代码)。

这个解决方案并不完全是迭代器的类型检查,而是提示我们可以朝哪个方向发展。给定您的PrintRange函数,我们假设我们需要为TIterator定义 3 个运算符 -operator*operator++operator !=

要检查是否定义了运算符,可以使用以下命令:

template<typename T>
struct has_deref_op{
private:
template<typename U>
static constexpr auto test(int) -> decltype(std::declval<U>().operator*() == 1,
std::true_type());
template<typename U>
static constexpr std::false_type test(...);
public:
static constexpr bool value = std::is_same<decltype(test<T>(0)),
std::true_type>::value;
}; 

此代码将检查T实现中是否存在operator*。然后,您可以添加一个static_assert,该将使用它来验证参数:

template<typename TIterator>
void PrintRange( TIterator beginIter, TIterator endIter )
{
static_assert(has_deref_op<TIterator>::value, "argument must implement operator*");
for( auto it = beginIter; it != endIter; ++it )
{
std::cout << *it << std::endl;
}
}

此解决方案有一个主要缺陷 - 它需要编写相当多的代码来简化错误消息。老实说,虽然这种方法效果很好,但我会坚持使用默认错误消息。这是不言自明的 - 如果您提供一个int,它没有定义operator*,您会收到一个错误。

编辑:在阅读了Edgar在他的回答中链接的问题后,它似乎建议实施与这种方法类似的is_iterator。我第一次没有仔细阅读的坏处

最新更新