概念可以限制类型成员变量吗?

Can concepts restrict the type member variables?

我正在使用 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++ 和模板元编程非常有经验,但目前感觉概念完全陌生。当然,如果这不可能,那么也很乐意接受这样的答案。

这感觉像是 Reflection TS 的完美之选,但是 clang 即使在 v13 中也没有以可访问的方式实现这一点(而且我认为 GCC 工作仍然在非主线分支中) .我探索了 C++17 的各种静态反射库,但它们都需要修改类型,这在这里是不可能的,因为我正在自省 lambda(并且 BadType 是定义在一个 3p 库,尽管这无关紧要)。

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

您可以使用 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
}

如果您愿意只检查直接嵌套对象在简单struct/aggregates中,请参阅.

如果您的目标是禁止在可调用对象中捕获某种类型的对象(例如,禁止在异步回调中捕获引用或禁止捕获某些不应从回调中访问的对象),那么它是不可能的一般情况即使有反射 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 可以考虑任何类型的擦除也根本不可能。