为何无法在std::function声明中使用受concept约束的auto参数?
std::function 仅支持接收具体、确定的函数签名作为模板参数,不识别带concept约束的auto参数占位符。
函数参数列表中出现con1 auto属于C++20的简写函数模板语法,本质是定义了一个函数模板,而非单个签名固定的普通函数。std::function的类型擦除逻辑是为固定签名的可调用对象设计的,无法承载泛型模板的语义,因此直接声明std::function<void (con1 auto functor)>会编译失败。
你写的带con1 auto参数的lambda能正常编译,是因为泛型lambda本质是个自带模板调用运算符的闭包类型,你没有将它绑定到要求固定签名的std::function实例上,自然不会触发错误。但单个std::function实例永远只能对应一个固定参数类型的签名,不可能实现“接受任意满足con1约束的参数”的泛型效果。
要实现延后注册、运行时替换可调用对象,同时保留concept编译期约束检查的效果,不需要强行适配std::function的原生语法,可以自定义两层轻量类型擦除包装,完全匹配你的需求:
首先实现参数的类型擦除视图,统一承载所有满足con1约束的参数:
#include <concepts> #include <string> #include <memory> #include <utility> #include <type_traits> template<typename T> concept con1 = requires(T t, std::string s){ { t[s] } -> std::same_as<std::string>; }; // 类型擦除视图:统一表示所有满足con1约束的参数 class Con1ArgView { private: struct HolderBase { virtual std::string access(std::string key) const = 0; virtual ~HolderBase() = default; }; template<con1 T> struct Holder : HolderBase { const T& ref; Holder(const T& t) : ref(t) {} std::string access(std::string key) const override { return ref[key]; } }; std::unique_ptr<HolderBase> impl_; public: // 仅允许满足con1约束的类型构造该视图 template<con1 T> Con1ArgView(const T& t) : impl_(std::make_unique<Holder<T>>(t)) {} // 对外暴露con1要求的接口 std::string operator[](std::string key) const { return impl_->access(std::move(key)); } };
再实现对应可调用对象的包装,替代你原本声明失败的function_signature:
// 可调用对象包装:支持持有任意可以接受Con1ArgView参数、返回void的可调用对象 class Con1Callable { private: struct HolderBase { virtual void call(Con1ArgView arg) = 0; virtual ~HolderBase() = default; }; template<typename F> requires std::invocable<F&, Con1ArgView> struct Holder : HolderBase { F func; Holder(F f) : func(std::move(f)) {} void call(Con1ArgView arg) override { func(arg); } }; std::unique_ptr<HolderBase> impl_; public: Con1Callable() = default; // 仅允许符合调用要求的可调用对象构造/赋值 template<typename F> requires std::invocable<F&, Con1ArgView> && (!std::is_same_v<std::decay_t<F>, Con1Callable>) Con1Callable(F f) : impl_(std::make_unique<Holder<F>>(std::move(f))) {} template<typename F> requires std::invocable<F&, Con1ArgView> && (!std::is_same_v<std::decay_t<F>, Con1Callable>) Con1Callable& operator=(F f) { impl_ = std::make_unique<Holder<F>>(std::move(f)); return *this; } // 对外调用接口:仅接受满足con1约束的参数 void operator()(con1 auto functor) const { impl_->call(Con1ArgView(functor)); } // 检查是否已持有有效可调用对象 explicit operator bool() const noexcept { return static_cast<bool>(impl_); } };
之后直接用这个Con1Callable作为你R类的模板参数即可,完全符合你的使用预期:
template<std::semiregular T> class R{ private: T functor; public: R() = default; void register_functor(T functor_) { functor = std::move(functor_);} // 按需实现调用逻辑即可 }; // 实例化时不需要绑定具体仿函数类型 auto rr = R<Con1Callable>(); // 任意阶段都可以注册、替换满足要求的可调用对象,无需固定类型 // rr.register_functor([](con1 auto obj){ /* 业务逻辑 */ });
这个实现所有约束检查都在编译期完成,不符合con1要求的参数、不符合签名要求的仿函数都会直接触发编译报错,不会引入额外的运行时风险。
内容的提问来源于stack exchange,提问作者ATK

