我遇到以下问题,由于 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
方法?我最初的想法是通过传递的回调中的另一个模板获取参数类型,但是当传递带有 lamda 时,编译器会抱怨类型不完整auto
。
10
最佳答案
3
如果您无法使用 C++23,您可以像这样模拟推导:
#include <type_traits>
template <class T, class FunctionT>
concept ValidSignature =
requires(FunctionT exec_fn, T& value) { exec_fn(value); };
class Foo
{
public:
template <class This, class FunctionT>
requires std::is_same_v<std::decay_t<This>, Foo>
&& ValidSignature<std::conditional_t<std::is_const_v<This>,
const int, int>, FunctionT>
friend auto getBar(This& foo, FunctionT callback) {
return callback(foo.m_abc);
}
private:
int m_abc{};
};
int main() {
Foo foo;
const Foo foo_const;
getBar(foo, [](int& abc) { return abc++; });
// getBar(foo, [](const int& abc) { return abc++; }); // does not compile
getBar(foo, [](auto& abc) { return abc++; });
// getBar(foo, [](const auto& abc) { return abc++; }); // does not compile
// getBar(foo_const, [](int& abc) { return abc++; }); // does not compile
getBar(foo_const, [](const int& abc) { return abc; });
// getBar(foo_const, [](auto& abc) { return abc++; }); // does not compile
getBar(foo_const, [](const auto& abc) { return abc; });
}
请注意,您并不严格需要std::conditional_t<std::is_const_v<This>, const int, int>
,但硬错误将发生在不同的地方。
|
在Red.Wave建议明确的 this之后,我或多或少地决定采用这种可以重用实现的方法(类似于patrick的示例,但更简洁一些):
C++20:
#include <concepts>
#include <type_traits>
#include <utility>
template <class T, class FunctionT>
concept ValidSignature =
requires(FunctionT exec_fn, T& value) { exec_fn(value); };
class Foo {
public:
template <class FunctionT>
auto getBar(FunctionT&& callback) {
return getBarImpl(*this, std::forward<FunctionT>(callback));
}
template <class FunctionT>
auto getBar(FunctionT&& callback) const {
return getBarImpl(*this, std::forward<FunctionT>(callback));
}
private:
static auto getBarImpl(auto& self, auto&& callback)
requires ValidSignature<decltype(self.m_abc), decltype(callback)>
{
constexpr bool IsConst = std::is_const_v<std::remove_reference_t<decltype(self)>>;
if constexpr (IsConst) {
// called from const
}
else {
// called from non-const
}
return std::forward<decltype(callback)>(callback)(self.m_abc);
}
int m_abc{};
};
int main() {
Foo foo;
const Foo foo_const;
foo.getBar([](int& abc) { return abc++; });
foo.getBar([](const int& abc) { return abc; });
foo.getBar([](auto& abc) { return abc++; });
foo.getBar([](const auto& abc) { return abc; });
// foo_const.getBar([](int& abc) { return abc++; });
foo_const.getBar([](const int& abc) { return abc; });
// foo_const.getBar([](auto& abc) { return abc++; });
foo_const.getBar([](const auto& abc) { return abc; });
}
对于未来的旅行者,你可以使用C++23做到这一点:
#include <concepts>
#include <type_traits>
#include <utility>
template <class T, class FunctionT>
concept ValidSignature =
requires(FunctionT exec_fn, T& value) { exec_fn(value); };
class Foo {
public:
auto getBar(this auto& self, auto&& callback)
requires ValidSignature<decltype(self.m_abc), decltype(callback)>
{
constexpr bool IsConst = std::is_const_v<std::remove_reference_t<decltype(self)>>;
if constexpr (IsConst) {
// called from const
}
else {
// called from non-const
}
return std::forward<decltype(callback)>(callback)(self.m_abc);
}
private:
int m_abc{};
};
int main() {
Foo foo;
const Foo foo_const;
foo.getBar([](int& abc) { return abc++; });
foo.getBar([](const int& abc) { return abc; });
foo.getBar([](auto& abc) { return abc++; });
foo.getBar([](const auto& abc) { return abc; });
// foo_const.getBar([](int& abc) { return abc++; });
foo_const.getBar([](const int& abc) { return abc; });
// foo_const.getBar([](auto& abc) { return abc++; });
foo_const.getBar([](const auto& abc) { return abc; });
}
3
-
decltype(self.m_abc)
不会给你const int
但是int
!
– -
@patrick 这为什么是个问题?
self.m_abc
从 const 方法调用时仍然是 const,如果您的 lambda 尝试将其修改为引用,则不会编译。
– -
1我想说的是,你最好写,
ValidSignature<int, decltype(callback)>
而不是ValidSignature<decltype(self.m_abc), decltype(callback)>
因为decltype(self.m_abc)
永远不会给你const int
。
–
|
关键问题是您的 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
vsint&
)。 -
这种
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 {};
};
6
-
它也只适用于 1 个概念,没有
int&
:。但是,现在我的下一步是尝试重用非常量方法,这本身就是另一个问题:
– -
@FrogTheFrog 我的错,我在发布答案后才注意到这一点。我正在研究这个问题,一旦找到解决方案,我就会更新它
– -
没问题,谢谢你的帮助!我确实有一个“解决方案”,但我不知道我对此有何感受……
– -
1@FrogTheFrog 通常,非 const 版本是根据 const 版本实现的,而不是相反。将非 const 转换为 const 比将非 const 转换为 const 要安全得多。缩小函数范围而不是扩大函数范围也更为明智。
– -
@FrogTheFrog 我不知道这是否有用,但这是我的看法。当 lambda sig 包含 const 时,我能够将实体 static_cast 为 const 来调用 const 实例。但问题
auto&
仍然存在。也许将我的试验与您的试验结合起来可以解决问题
–
|
auto&
可以推断为const int&
和int&
。–
[](auto& abc){ return abc++; }
不是 SFINAE 友好的,你可能需要!std::is_const_v<std::remove_reference_t<decltype(abc)>>
。–
ValidNonConstSignature
按照ValidConstSignature
:来定义concept ValidNonConstSignature = ValidConstSignature<T, F> or requires
。这比ValidNonConstSignature
更具体ValidConstSignature
。更好的选择是新的显式语法:decltype(auto) Foo::getbar(this auto& self, auto&& f) requires std::invocable<decltype(f), decltype(self.m_abc)> { return f(self.m_abc)};
–
ValidConstSignature<int, decltype([](auto& abc) { return abc++; })>
触发硬错误,而不是替换失败。–
A or B
不如A
–
|