为什么使用 find_first_or_default 函数扩展 std 命名空间会阻止模板推导工作

Why does extending the std namespace with a find_first_or_default function stop template deduction from working

提问人:Matthew Dodd 提问时间:6/30/2023 最后编辑:GilesDMiddletonMatthew Dodd 更新时间:6/30/2023 访问量:78

问:

以下函数无法编译(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
C++ STL C++17 标准

评论

2赞 Yksisarvinen 6/30/2023
您不得向命名空间添加任何内容(极少数例外)。编译器可以用这样的代码做任何事情。std
0赞 molbdnilo 6/30/2023
请比“编译失败”更具体。
0赞 pptaszni 6/30/2023
en.cppreference.com/w/cpp/language/extending_std
1赞 pptaszni 6/30/2023
typename std::iterator_traits<typename Container::iterator>::value_type通过此修改,它可以在namespace std
0赞 Matthew Dodd 6/30/2023
太棒了,谢谢@pptaszni。添加额外的“typename”可修复该错误。

答:

2赞 Jan Schultke 6/30/2023 #1

如注释中所述,不允许向命名空间添加任何添加符号,只有在明确允许的情况下才能专化某些类。 您的程序格式不正确,但这不是它无法编译的原因。std

在:

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

::iterator依赖于 template 参数,因此除非消除歧义,否则它将被解释为静态数据成员。替换将失败,无法推断,并且您无法调用该函数。要解决此问题,请使用消歧器:ContainerTtypename

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

评论

0赞 Matthew Dodd 6/30/2023
很棒(和彻底)的帖子,非常感谢。我将实施这些更改。