c++ 为什么用find_first_or_default函数扩展std命名空间会阻止模板演绎工作

gywdnpxw  于 2023-07-01  发布在  其他
关注(0)|答案(1)|浏览(134)

以下函数无法编译(ISO C++ 17标准,Visual Studio 2019)

namespace std
{
    // Find the first item which matches the predicate. Returns a default constructed item (probably nullptr) on failure.
    template <class Container, typename Predicate, typename T = typename std::iterator_traits<Container::iterator>::value_type>
    T find_first_or_default(const Container& container, Predicate predicate, T defaultVal = T()) 
    {
        auto it = std::find_if(container.begin(), container.end(), predicate);
        if (it == container.end())
            return defaultVal;
        return *it;
    }
};

void Test()
{
    std::vector<int> vec = { 1,2,3 };
    int i = std::find_first_or_default(vec, [](int val) {return val % 2 == 0; });
}

如果我将命名空间更改为'std'以外的任何名称,它都可以编译。我假设std名称空间中的其他东西使模板推导失败。
以下是错误:

Error   C2783   'T std::find_first_or_default(const Container &,Predicate,T)': could not deduce template argument for 'T'   
Error   C2672   'std::find_first_or_default': no matching overloaded function found
9jyewag0

9jyewag01#

正如注解中提到的,不允许向std名称空间添加任何附加符号,只有在显式允许时才专门化一些类。你的程序是病态的,但这不是它无法编译的原因。
在:

typename T = typename std::iterator_traits<Container::iterator>::value_type

::iterator依赖于Container模板参数,因此除非消除歧义,否则它将被解释为静态数据成员。那么替换将失败,T无法推导,并且您无法调用该函数。要解决这个问题,请使用typename消歧器:

typename T = typename std::iterator_traits<typename Container::iterator>::value_type

如果你经常编写这样的代码,定义一个类似于std::ranges::range_value_t的方便别名也是有意义的:

// note 1: we are not guaranteed to have a T::iterator alias, e.g. when T is an array
template <typename T>
using range_iter_t = decltype(std::begin(std::declval<T>()));

template <typename T>
using range_value_t = typename std::iterator_traits<range_iter_t<T>>::value_type;

// used like:

// note 2: conventional name is 'Range', not 'Container'
//         not every range is a container
template <class Range, typename Predicate, typename T = range_value_t<Range>>
// note 3: use perfect forwarding for r and and predicate because of one-time use
//         in the algorithm. std::ranges::find_if also takes Range&&
T find_first_or_default(Range&& r, Predicate&& predicate, T defaultVal = T()) 
{
    // note 4: avoid calling end() twice, end() isn't always trivial    
    // note 5: use std::begin/std::end to make this function work with C-style arrays
    auto end = std::end(r);
    // note 6: perfectly forward the predicate, don't just copy
    auto it = std::find_if(std::begin(r), end, std::forward<Predicate>(predicate));
    if (it == end)
        return defaultVal;
    return *it;
}
// note 7: it would also make sense to have a second overload which takes two
//         iterators, not just a function that takes a range

相关问题