【问题标题】:How to assert type of template argument STL iterator type如何断言模板参数的类型 STL 迭代器类型
【发布时间】:2018-06-11 02:38:43
【问题描述】:

假设我想编写一个通用函数,从集合中打印出标准输出范围。既然它应该是通用的,我假设......

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 断言所有已知的可能性.. 但是由于没有基类,如何断言函数的参数是 ANY 迭代器?

static_assert(std::is_base_of::iterator >::value);

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

【问题讨论】:

  • 为什么开始和结束迭代器的类型不同?他们应该是同一个
  • 如果您在函数中添加了良好的文档,那么用户无需检查您的任何模板代码,您也无需投入任何额外的精力来验证输入。让编译器验证!

标签: c++ templates


【解决方案1】:

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

另一方面,如果你决定打印一条有意义的消息,你可以实现一个自定义类型特征来检测迭代器,正如here 所解释的那样,然后将它与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;
    }
}

【讨论】:

    【解决方案2】:

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

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

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

    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;
    }; 
    

    此代码将检查operator*T 实现中的存在。然后,您可以添加一个 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。我第一次不仔细阅读不好

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多