【问题标题】:Can concepts restrict the type member variables?概念可以限制类型成员变量吗?
【发布时间】:2021-04-14 18:17:23
【问题描述】:

我正在使用 C++20,我想知道 Concepts 是否有可能解决这种情况。

假设我有一个函数:

template <typename F>
void doSomething(F f) {
  f();
}

doSomething 接受一个可调用对象(技术上是 lambda,但我认为这无关紧要)& 我想确保 F 的成员变量不具有 T 类型。例如,我想要:

BadType t;
int x = ...;
double y = ...;
doSomething([t = kj::mv(t), x, y] { ... }); // I want a compile error
doSomething([x, y] { ... }); // No error.

同样,我想以类似的方式验证可调用的参数:

doSomething([x, y](BadType t) { ... }); // I want a compile error
doSomething([x, y](std::vector<int> c) { ... }); // No compile error

我不知道这是否会使事情复杂化,但从技术上讲,BadType 本身就是一个模板类型(无论模板值如何,我都想禁用所有实例)。

一个可接受的答案可能是一个提供示例的答案,但我也很高兴有人认为我应该能够拼凑完成的优秀教程。我在 C++ 和模板元编程方面非常有经验,但目前的概念感觉像是完全陌生的东西。当然,如果这是不可能的,那么也很乐意接受这样的答案。

这感觉像是反射 TS 非常适合的东西,但是即使在 v13 中,clang 也无法以一种可访问的方式实现这一点(我认为 GCC 的工作仍然在非主线分支中)。我已经探索了 C++17 的各种静态反射库,但它们都需要修改类型,这在此处是不可能的,因为我正在内省 lambda(并且BadType 是在 3p 中定义的类型图书馆虽然这不重要)。

怀疑答案一定是否定的,因为我传入的每个 lambda 都会有一组任意的名称,用于捕获的变量以及我见过的试图强制执行的概念的唯一示例成员变量的类型需要一个已知的变量名,但这对于概念大师来说可能是一个有趣的挑战。

【问题讨论】:

  • "确保 F 的成员变量没有类型 T" 这是完全不可能的。 “验证可调用的参数”目前还不太清楚你想要验证什么。您正在调用不带参数的可调用对象,因此接受任何参数的可调用对象将导致编译错误。
  • 阅读这篇文章,了解为什么要转发函子:stackoverflow.com/questions/24779910/…
  • @n.'pronouns'm.: ""确保 F 的成员变量不具有 T 类型"这完全不可能"。错误的。看我的回答。
  • 你为什么要这个?我很难找到这样做的理由。
  • @VittorioRomeo lambda 既不是简单的结构也不是聚合。

标签: c++ c++20 c++-concepts


【解决方案1】:

您可以使用boost::pfr 轻松检查一个简单的struct/聚合T 是否包含任何类型为TType 的字段:

#include <boost/pfr.hpp>

template <typename TType, typename T>
[[nodiscard]] constexpr bool has_any_data_member_of_type() noexcept
{
    return []<std::size_t... Is>(std::index_sequence<Is...>)
    {
        return (std::is_same_v<boost::pfr::tuple_element_t<Is, T>, TType> || ...);
    }(std::make_index_sequence<boost::pfr::tuple_size_v<T>>{});
}

struct a { int i; float f; char c; };
struct b { int i;          char c; };

static_assert( has_any_data_member_of_type<float, a>());
static_assert(!has_any_data_member_of_type<float, b>());

live example on godbolt.org


然后您可以轻松地为此定义一个概念:

template <typename T, typename TType>
concept having_any_data_member_of_type = has_any_data_member_of_type<TType, T>();

很遗憾,由于 lambda 表达式不是聚合,您将无法将它们与 has_any_data_member_of_type 一起使用。但是,自定义可调用对象有效:

struct my_callable
{
    float f;
    float operator()() { return f; }
};

void f0(having_any_data_member_of_type<float> auto f)
{
    (void) f();
}

int main()
{
    f0(my_callable{}); // OK
}

【讨论】:

  • 这是一个非常简洁实用的链接。不幸的是,我需要 lambdas 的解决方案。在这种情况下,自定义 callable 将不起作用(大型代码库,因此手动转换将太耗时,除非我错过了一些方法来做到这一点)。
  • 我不相信有一种方法可以在 C++20 中模拟对 lambda 捕获的反射。
【解决方案2】:

如果您对在简单结构/聚合中仅检查对象的直接嵌套感到满意,请参阅Vittorio's answer

如果您的目标是禁止在可调用对象中捕获某种类型的对象(例如,禁止在异步回调中捕获引用或禁止捕获某些不应从回调中访问的对象),那么通常是不可能的即使有反射 TS 也是如此。

简单的反例:

BadType t;
doSomething([t = std::any(kj::mv(t))]{...}); // Even reflection TS could not do anything with this.

另一个:

struct Fun {
    std::any v;
    void operator()() {
       ....
    }
}

Fun f = ...
doSomething(std::move(f)); // Event more powerful reflection than reflection TS (reflection with statements reflection) could not do anything with this.

即使nesting in multiple layers of objects 可以被考虑在内,任何类型的擦除都不能。

【讨论】:

  • 当然,在我的用例中,我碰巧没有任何类型擦除的实例需要担心,所以这是一个极端的反例(即反射 TS 如果可用,它应该可以工作) .只是一个中间立场,我没有简单的结构/聚合 & 我需要在具有我想要强制执行的已知当前模式的 lambdas 上强制执行此操作。也许 semgrep 会是一个很好的中间地带。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-23
  • 2018-04-25
  • 1970-01-01
  • 2021-08-29
相关资源
最近更新 更多