我遇到以下问题,由于 lambda 的自动参数而选择了不正确的方法。
请参阅以下示例:
template <class T, class FunctionT>
concept ValidNonConstSignature = requires(FunctionT exec_fn, T& value) { exec_fn(value); };
template <class T, class FunctionT>
concept ValidConstSignature = requires(FunctionT exec_fn, const T& value) { exec_fn(value); };
class Foo {
public:
template <class FunctionT> requires ValidNonConstSignature<int, FunctionT>
auto
getBar(FunctionT callback) {
return callback(m_abc);
}
template <class FunctionT> requires ValidConstSignature<int, FunctionT>
auto
getBar(FunctionT callback) const {
return callback(m_abc);
}
private:
int m_abc {};
};
int main()
{
Foo foo;
foo.getBar([](int& abc){ return abc++; });
foo.getBar([](const int& abc){ return abc++; }); // correct compiler error: increment of read-only reference ‘abc’
foo.getBar([](auto& abc){ return abc++; }); // incorrect compiler error: increment of read-only reference ‘abc’
foo.getBar([](const auto& abc){ return abc++; }); // correct compiler error: increment of read-only reference ‘abc’
return 0;
}
因为
getBar(FunctionT callback) const
的存在,导致foo.getBar([](auto& abc){ return abc++; });
无法编译。
如果删除
getBar(FunctionT callback) const
方法,foo.getBar([](auto& abc){ return abc++; });
编译得很好。
有没有办法以某种方式增强概念以正确选择
non-const
方法?我最初的想法是通过另一个模板从传递的回调中获取参数类型,但是当传递带有 auto
的 lamda 时,编译器抱怨类型不完整。
关键问题是你的 lambda 对 SFINAE 不友好。这个想法是,带有
auto&
参数的 lambda 可能无法按照概念、重载或模板约束进行可预测的行为,因为 lambda 不会通过替换失败自然地拒绝某些类型,例如 const&
。
类型不匹配问题通常很容易解释,但这个不是,我会尽力解释清楚。
这是你的
ValidNonConstSignature
概念
template <class T, class FunctionT>
concept ValidNonConstSignature = requires(FunctionT exec_fn, T& value) { exec_fn(value); };
在requires子句中,你有
(FunctionT exec_fn, T& value) { exec_fn(value)
,即lambda应该接受一个int&
,这完全没问题,直到你用T = int
实例化这个概念。该概念用于验证 lambda 的类型变得令人困惑,因为 T = int
但 requires
子句说 exec_fn(int& value)
,这就是问题的根源。
这个概念是为
T = int
实例化的,但是当它检查 lambda 签名时,它期望 lambda 与 int&
一起工作,这就是为什么我在回答开始时提到你的 lambda 不是 SFINAE 友好的。
我们不能说遵循相同的模式总是会导致类型不匹配。如果是这种情况,我们要么修复该语言,要么避免使用它,因为我们知道它不会 100% 有效。但实际上我可以说这个可能导致类型不匹配取决于lambda的定义方式特别是如果你在lambda中使用
auto&
,由于类型推导规则,它的行为略有不同。
总而言之,让我们看一下您的 lambda
foo.getBar([](auto& abc) { return abc++; });
lambda 参数推导为
int&
但你的概念是
受 ValidNonConstSignature<int, FunctionT>
约束。这
不匹配会使编译器感到困惑,因为约束需要一个
不同的实例化(int
vs int&
)。
auto&
推导导致编译器报告 lambda
签名与所需的概念不符,即使auto&
逻辑上应该可行。
解决这个问题的一个简单方法就是用
int&
而不是 int
来实例化这个概念。
这可以确保在传递以下 lambda 时正确保留引用语义:
foo.getBar([](auto& abc) { return abc++; });
编译器知道
auto
被推导为 int&
并且这与 int&
约束中的 ValidNonConstSignature
匹配,因此概念检查成功。
这里是概念实例化的变化。
class Foo {
public:
template <class FunctionT> requires ValidNonConstSignature<int&, FunctionT>
auto
getBar(FunctionT callback) {
return callback(m_abc);
}
template <class FunctionT> requires ValidConstSignature<int&, FunctionT>
auto
getBar(FunctionT callback) const {
return callback(m_abc);
}
private:
int m_abc {};
};