首页 > 解决方案 > 只接受指向算术类型的迭代器的模板

问题描述

我正在尝试自学 SFINAE 模式,对于我正在写的东西,我想编写一个函数,该函数接受start,end迭代器到算术类型的值(例如,用于求和)。这就是我想出的:

我的main.cpp

#include <iostream>
#include <vector>

#include "summer.hpp"


int main()
{
    std::vector<double> vec {0.1, 0.2, 0.3};  // these are OK
    auto sum = summer(vec.begin(), vec.end());
    std::cout << sum << std::endl;
    std::vector<std::string> vec2 {"a", "b"};  // these should be rejected
    auto sum2 = summer(vec2.begin(), vec2.end());
    std::cout << sum2 << std::endl;
    return 0;
}

然后summer.hpp

#include <type_traits>

template <
    typename Iter,
    typename = typename std::enable_if_t<std::is_arithmetic<Iter>::value_type, Iter>,
    typename T = typename Iter::value_type
>
T summer(Iter start, Iter end)
{
    T sum{};
    for (auto it = start; it != end; it++)
    {
        sum += *it;
    }
    return sum;
}

我从这个答案中获取了上面的 SFINAE 位,并对其进行了调整以使用与迭代器指向的类型相对应的类型特征 ( value_type)。但我正在努力编译它,我收到一连串关于value_type被解析为非类型但产生类型和提示的抱怨,我应该在它前面加上添加typename(这是错误的):

$ g++ --std=c++17 main.cpp  && ./a.out 
main.cpp: In function ‘int main()’:
main.cpp:10:45: error: no matching function for call to ‘summer(std::vector<double>::iterator, std::vector<double>::iterator)’
   10 |     auto sum = summer(vec.begin(), vec.end());
      |                                             ^
In file included from main.cpp:4:
summer.hpp:8:3: note: candidate: ‘template<class Iter, class, class T> T summer(Iter, Iter)’
    8 | T summer(Iter start, Iter end)
      |   ^~~~~~
summer.hpp:8:3: note:   template argument deduction/substitution failed:
summer.hpp:5:5: error: dependent-name ‘std::is_arithmetic<_Tp>::value_type’ is parsed as a non-type, but instantiation yields a type
    5 |     typename = typename std::enable_if_t<std::is_arithmetic<Iter>::value_type, Iter>,
      |     ^~~~~~~~
summer.hpp:5:5: note: say ‘typename std::is_arithmetic<_Tp>::value_type’ if a type is meant
main.cpp:13:48: error: no matching function for call to ‘summer(std::vector<std::__cxx11::basic_string<char> >::iterator, std::vector<std::__cxx11::basic_string<char> >::iterator)’
   13 |     auto sum2 = summer(vec2.begin(), vec2.end());
      |                                                ^
In file included from main.cpp:4:
summer.hpp:8:3: note: candidate: ‘template<class Iter, class, class T> T summer(Iter, Iter)’
    8 | T summer(Iter start, Iter end)
      |   ^~~~~~
summer.hpp:8:3: note:   template argument deduction/substitution failed:
summer.hpp:5:5: error: dependent-name ‘std::is_arithmetic<_Tp>::value_type’ is parsed as a non-type, but instantiation yields a type
    5 |     typename = typename std::enable_if_t<std::is_arithmetic<Iter>::value_type, Iter>,
      |     ^~~~~~~~
summer.hpp:5:5: note: say ‘typename std::is_arithmetic<_Tp>::value_type’ if a type is meant

我相信我已经放在typename正确的地方,如果我要摆脱算术限制编译就好了,但我不希望它接受例如string向量。

我究竟做错了什么?

标签: c++c++11templatesc++17sfinae

解决方案


好吧,我从哪里开始...

  • typename std::enable_if_t<...>错了,删typename::仅当模板参数右侧存在时才需要它,例如在typename std::enable_if<...Iter...>::type.

  • ::value_type放错地方了,应该就在后面Iter

  • ...::value_type需要typename

  • std::is_arithmetic<...>必须是std::is_arithmetic_v<...>std::is_arithmetic<...>::value

  • 在这种情况下,第二个模板参数std::enable_if_t无关紧要,可以删除。

所以我们最终得到了这段代码,它至少可以工作:

template <
    typename Iter,
    typename = std::enable_if_t<std::is_arithmetic_v<typename Iter::value_type>>,
    typename T = typename Iter::value_type
>

但是等等,还有更多:

  • typename T = typename Iter::value_type是对模板参数的滥用,可以通过指定自定义模板参数来破坏。

  • typename = std::enable_if_t<...>是一个弱 SFINAE,因为用户可以通过提供任何模板参数来规避它。更喜欢这种形式:std::enable_if_t<..., std::nullptr_t> = nullptr,它没有这个问题。

  • 您应该使用std::iterator_traits而不是直接从迭代器中读取::value_type,因为某些迭代器(例如指针)没有它(感谢@NathanPierson)。

  • auto it = start;执行不必要的副本。可以直接操作start

所以最终版本看起来像这样:

template <
    typename Iter,
    std::enable_if_t<std::is_arithmetic_v<typename std::iterator_traits<Iter>::value_type>, std::nullptr_t> = nullptr
>
typename std::iterator_traits<Iter>::value_type summer(Iter start, Iter end)
{
    typename std::iterator_traits<Iter>::value_type sum{};
    for (; start != end; start++)
        sum += *start;
    return sum;
}

推荐阅读