我有一个类,它包装了不同类型的容器/视图,并且可以使用
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 重载?在这种情况下,我该怎么做才能让编译器选择非常量重载?
在重载决议中检查概念
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