选择基于带有自动参数的巴巴签名的非常量方法

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

我遇到以下问题,由于 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 时,编译器抱怨类型不完整。

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

关键问题是你的 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++; });
  1. lambda 参数推导为

    int&
    但你的概念是 受
    ValidNonConstSignature<int, FunctionT>
    约束。这 不匹配会使编译器感到困惑,因为约束需要一个 不同的实例化(
    int
    vs
    int&
    )。

  2. 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 {};
};
© www.soinside.com 2019 - 2024. All rights reserved.