以函数为参数的成员函数:为什么编译器在传递带有 auto& 参数的 lambda 时选择 const 重载

问题描述 投票:0回答:1

我有一个类,它包装了不同类型的容器/视图,并且可以使用

forEach
成员函数调用所有元素上的函数。
forEach
函数有不同的重载,它们的区别在于它们对函数类型(它是一个模板)的约束和它们的常量。现在我注意到,当调用具有
auto&
参数的 lambda 的非常量实例时,编译器错误地选择了 const 重载,然后无法编译。下面是一个说明问题的精简示例:

#include <type_traits>
#include <concepts>
#include <vector>
#include <span>

template <class, class>
struct FunctionWithSignatureOrImplicitlyConvertible : std::false_type {};

template <class Fn, class Ret, class... Args>
struct FunctionWithSignatureOrImplicitlyConvertible<Fn, Ret (Args...)>
{
    static constexpr bool value = requires (Fn&& t, Args&&... args) { { t (args...) } -> std::convertible_to<Ret>; };
};

template <class Fn, class Signature>
concept functionWithSignatureOrImplicitlyConvertible = FunctionWithSignatureOrImplicitlyConvertible<Fn, Signature>::value;

template <class T>
struct Foo
{
    using ValueType = typename T::value_type;
    T& vec;

    Foo (T& src) : vec (src) {}

    template <functionWithSignatureOrImplicitlyConvertible<void (ValueType&)> Fn>
    void forEach (Fn&& fn)
    {
        for (auto& e : vec)
            fn (e);
    }

    template <functionWithSignatureOrImplicitlyConvertible<void (const ValueType&)> Fn>
    void forEach (Fn&& fn) const
    {
        for (auto& e : vec)
            fn (e);
    }
};

int main()
{
    std::vector<float> v { 1.0f, 2.0f, 3.0f, 4.0f };
    std::span s (v);

    Foo a (v);
    Foo b (s);

    a.forEach ([] (auto& e) { e *= 2.0f; }); // -> error: cannot assign to variable 'e' with const-qualified type 'const float &'
    a.forEach ([] (float& e) { e *= 2.0f; });

    b.forEach ([] (auto& e) { e *= 2.0f; }); // -> error: cannot assign to variable 'e' with const-qualified type 'const float &'
    a.forEach ([] (float& e) { e *= 2.0f; });
    
    return 0;
}

这里是Compiler Explorer上的实例。

现在我的问题是:为什么在

auto&
的情况下选择 const 重载?在这种情况下,我该怎么做才能让编译器选择非常量重载?

c++ c++20 c++-concepts
1个回答
0
投票

在重载决议中检查概念

functionWithSignatureOrImplicitlyConvertible<(closure type), void (const ValueType&)>
时,它必须实例化lambda的主体,其中auto参数推导为
const float
以弄清楚自动返回类型是什么。

这不是 SFINAE 上下文,所以

const float& e
e *= 2.0f
是一个硬错误。这是在选择
forEach
功能之前发生的。

如果您使其支持 SFINAE 或删除自动返回类型,它就会消失:

a.forEach ([] (auto& e) -> decltype(e*=2.0f, void()) { e *= 2.0f; });
// functionWithSignatureOrImplicitlyConvertible<(closure type), void (const ValueType&)>
// will be false

a.forEach ([] (auto& e) -> void { e *= 2.0f; });
// functionWithSignatureOrImplicitlyConvertible<(closure type), void (const ValueType&)>
// will be true, but the non-const forEach overload will be picked anyways
© www.soinside.com 2019 - 2024. All rights reserved.