如何用模板右值引用替代万能引用实现construct重载匹配?
如何让万能引用参数正确匹配const-ref和右值ref重载
我有一个类模板,它接受符合std::invocable<Args...>约束的万能引用参数。我希望根据参数的引用类型,将其转发给对应的construct重载:左值匹配const-ref重载,右值匹配右值ref重载。但问题在于,模板右值引用作为万能引用时,会“贪婪”地匹配右值ref重载,导致左值实例化也调用了右值版本,而非预期的const-ref版本。
问题代码示例
#include <concepts> #include <cstdio> #include <utility> template <typename... Args> struct function { template <std::invocable<Args...> Cb> function(Cb&& fn) { construct(std::forward<Cb>(fn)); } template<typename Cb> auto construct(const Cb&) { printf("Overload for const-ref called!\n"); } template <typename Cb> auto construct(Cb&&) { printf("Overload for rvalue-ref called!\n"); } }; struct functor { auto operator()() { printf("Functor called!\n"); } }; int main() { functor foo1; function myfunc{foo1}; function myfunc2{functor{}}; }
当前输出
Overload for rvalue-ref called! Overload for rvalue-ref called!
更新说明
原案例可能不够直观,实际场景中可能涉及需要根据参数的引用类型执行不同的拷贝/移动逻辑(比如保存回调的副本或移动资源),核心问题仍是万能引用导致的重载匹配歧义。
解决方案
方法1:用requires约束限制右值重载的匹配范围
通过给右值ref版本的construct添加约束,使其仅当参数不是左值引用时才参与重载解析,从而让左值参数优先匹配const-ref重载:
#include <concepts> #include <cstdio> #include <utility> #include <type_traits> template <typename... Args> struct function { template <std::invocable<Args...> Cb> function(Cb&& fn) { construct(std::forward<Cb>(fn)); } template<typename Cb> auto construct(const Cb&) { printf("Overload for const-ref called!\n"); } template <typename Cb> // 仅当Cb不是左值引用时,此重载才生效 requires (!std::is_lvalue_reference_v<Cb>) auto construct(Cb&&) { printf("Overload for rvalue-ref called!\n"); } }; struct functor { auto operator()() { printf("Functor called!\n"); } }; int main() { functor foo1; function myfunc{foo1}; function myfunc2{functor{}}; }
方法2:标签分发(Tag Dispatch)
通过显式传递标签来区分左值/右值场景,避免重载匹配歧义:
#include <concepts> #include <cstdio> #include <utility> #include <type_traits> // 定义标签类型 struct lvalue_tag {}; struct rvalue_tag {}; template <typename... Args> struct function { template <std::invocable<Args...> Cb> function(Cb&& fn) { // 根据参数的引用类型分发到不同重载 if constexpr (std::is_lvalue_reference_v<Cb>) { construct(std::as_const(fn), lvalue_tag{}); } else { construct(std::forward<Cb>(fn), rvalue_tag{}); } } template<typename Cb> auto construct(const Cb&, lvalue_tag) { printf("Overload for const-ref called!\n"); } template <typename Cb> auto construct(Cb&&, rvalue_tag) { printf("Overload for rvalue-ref called!\n"); } }; struct functor { auto operator()() { printf("Functor called!\n"); } }; int main() { functor foo1; function myfunc{foo1}; function myfunc2{functor{}}; }
修正后输出
Overload for const-ref called! Overload for rvalue-ref called!
内容的提问来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

