为何约束成员函数优先选择const重载而非非const版本?
问题原因
当使用auto&参数的lambda时,这个lambda具备通用性——它既可以接受std::string&,也可以接受const std::string&,因此两个Apply重载的约束条件(std::invocable<const std::string&>和std::invocable<std::string&>)都能被满足。
在重载决议阶段,编译器无法区分两个模板重载的匹配优先级,最终错误选择了const版本的Apply。此时lambda的auto&会被推导为const std::string&,你在lambda中对s的赋值操作违反了const引用的不可修改规则,导致编译失败。
而显式指定std::string&参数的lambda只能接受非const引用,因此只有非const版本的Apply能满足约束,编译正常。
解决思路
思路一:给const版本添加排他性约束
修改const版本的Apply,让它仅在Func不能接受非const引用时才生效。这样当Func既能接受非const也能接受const引用时,编译器会优先选择非const版本:
#include <concepts> #include <utility> #include <functional> #include <string> class Foo { public: // const版本:仅当Func不能接受非const std::string&时生效 template <typename Func> requires std::invocable<Func, const std::string&> && !std::invocable<Func, std::string&> decltype(auto) Apply(Func func) const { return std::invoke(func, m_Inner); } // 非const版本:只要能接受非const std::string&就生效 template <typename Func> requires std::invocable<Func, std::string&> decltype(auto) Apply(Func func) { return std::invoke(func, m_Inner); } private: std::string m_Inner; }; int main() { Foo sync{}; // 正常工作 sync.Apply([](std::string& s) { s = "Hello, World!"; }); // 现在正常工作,会选择非const版本 sync.Apply([](auto& s) { s = "Hello, World!"; }); }
思路二:合并为单一重载,根据this类型分支处理
将两个重载合并,利用if constexpr根据当前对象的const属性,以及Func的可调用性,选择合适的参数传递方式:
#include <concepts> #include <utility> #include <functional> #include <string> class Foo { public: // 非const对象调用的版本 template <typename Func> decltype(auto) Apply(Func func) & { if constexpr (std::invocable<Func, std::string&>) { return std::invoke(func, m_Inner); } else if constexpr (std::invocable<Func, const std::string&>) { return std::invoke(func, static_cast<const std::string&>(m_Inner)); } else { static_assert(false, "Func无法接受std::string&或const std::string&"); } } // const对象调用的版本 template <typename Func> requires std::invocable<Func, const std::string&> decltype(auto) Apply(Func func) const& { return std::invoke(func, m_Inner); } private: std::string m_Inner; }; int main() { Foo sync{}; sync.Apply([](std::string& s) { s = "Hello, World!"; }); sync.Apply([](auto& s) { s = "Hello, World!"; }); const Foo const_sync{}; // const对象只能调用接受const引用的Func const_sync.Apply([](const auto& s) { /* ... */ }); }
这种方式更灵活,既能处理各种可调用对象,也能明确区分const和非const对象的调用逻辑。
内容的提问来源于stack exchange,提问作者DNKpp
相关产品推荐
相关产品推荐

