如何编写避免窄化转换与切片的LosslessInvocable概念?
实现无损失可调用对象概念:避免窄化转换与对象切片
我需要设计一个C++概念,用于约束可调用对象,同时满足核心目标:避免窄化转换、防止对象切片,且兼容鸭子类型模板中合法的可调用场景。
初始代码与问题
注释掉概念约束时,以下代码可正常运行并输出所有内容:
#include <cstdint> #include <concepts> #include <print> #include <type_traits> template<typename T> struct ValueHolder { using ValueType = T; T value; }; template <typename From, typename To> concept non_narrowing = requires(From f) { To{f}; }; template< typename Value, typename T = std::remove_reference_t<Value>::ValueType, typename Callable > /* requires // 无法接受鸭子类型中合法的可调用对象 std::invocable<Callable, T> // 可编译但未实际约束,且重复函数体代码 // requires (Value&& val, Callable&& callable) { // std::forward<Callable>(callable)(std::forward<Value>(val).value); // } // 编译失败:无法推导Arg // non_narrowing<T, Arg> && std::invocable<Callable, Arg> // 编译失败:无法推导Arg,且重复代码 // requires (Value&& val, Callable&& callable) { // std::forward<Callable>(callable)(non_narrowing<decltype(std::forward<Value>(val).value), Arg>); // } */ auto callOnValue(Value&& val, Callable&& callable) { std::forward<Callable>(callable)(std::forward<Value>(val).value); } void printI32(std::int32_t) { std::println("i32"); } void printI32Ref(std::int32_t&) { std::println("i32&"); } void printI32RefRef(std::int32_t&&) { std::println("i32&&"); } void printConstI32Ref(const std::int32_t &) { std::println("const i32&"); } void printI16(std::int16_t) { std::println("i16"); } void printI64(std::int64_t) { std::println("i64"); } struct Base {}; struct Derived: public Base {}; void printBase(Base) { std::println("Base"); } void printConstBaseRef(const Base&) { std::println("const Base&"); } void printDerived(Derived) { std::println("Derived"); } void printConstDerivedRef(const Derived&) { std::println("const Derived&"); } int main() { ValueHolder i{static_cast<std::int32_t>(42)}; callOnValue(i, printI32); callOnValue(i, printI32Ref); callOnValue(ValueHolder{static_cast<std::int32_t>(42)}, printI32RefRef); const auto ci{i}; callOnValue(ci, printI32); callOnValue(ci, printConstI32Ref); callOnValue(i, printI16); callOnValue(i, printI64); ValueHolder d{Derived{}}; callOnValue(d, printBase); callOnValue(d, printConstBaseRef); callOnValue(d, printDerived); callOnValue(d, printConstDerivedRef); }
尝试添加约束时遇到的问题:
- 启用
requires std::invocable<Callable, T>时,无法调用printI32Ref(std::invocable仅检查传递T类型参数的可行性,无法匹配左值引用参数); - 启用函数体逻辑的requires约束时,仅能保证编译通过,未真正约束可调用对象,且重复了函数体代码;
- 引入
typename Arg搭配non_narrowing与std::invocable时,会出现Arg无法推导的错误。
需求明确
需要实现LosslessInvocable<Callable, T>概念,满足:
- 支持所有合法传递方式:可调用对象接受值传递、const引用传递、左值引用传递、右值引用传递;
- 阻止窄化转换:
printI16无法编译(int32_t转int16_t属于窄化),printI64可编译(int32_t转int64_t无窄化); - 支持逆变:接受
Base&或Base*的可调用对象可用于ValueHolder<Derived>; - 阻止对象切片:接受
Base值传递的可调用对象无法用于ValueHolder<Derived>。
解决方案
核心概念实现
通过拆分辅助概念,组合成满足所有需求的LosslessInvocable:
#include <cstdint> #include <concepts> #include <print> #include <type_traits> #include <utility> #include <tuple> // 检查非窄化转换:From可无损失转换为To template<typename From, typename To> concept non_narrowing = requires(From f) { To{f}; }; // 检查是否为引用类型 template<typename T> concept is_reference = std::is_reference_v<T>; // 检查是否为指针类型 template<typename T> concept is_pointer = std::is_pointer_v<T>; // 辅助Trait:获取可调用对象的参数类型 template<typename Callable> struct function_traits : function_traits<decltype(&Callable::operator())> {}; template<typename Class, typename Ret, typename... Args> struct function_traits<Ret(Class::*)(Args...) const> { using args = std::tuple<Args...>; }; template<typename Ret, typename... Args> struct function_traits<Ret(*)(Args...)> { using args = std::tuple<Args...>; }; // 检查可调用对象是否接受T的任意引用类型(左值/右值/const引用) template<typename Callable, typename T> concept invocable_with_reference_to = std::invocable<Callable, T&> || std::invocable<Callable, const T&> || std::invocable<Callable, T&&>; // 检查可调用对象是否接受T的基类的引用/指针(逆变,无切片) template<typename Callable, typename T> concept invocable_with_contravariant_base = std::is_class_v<T> && std::invocable<Callable, T> && // 确保参数是引用或指针,避免切片 is_reference<std::tuple_element_t<0, typename function_traits<Callable>::args>> || is_pointer<std::tuple_element_t<0, typename function_traits<Callable>::args>>; // 最终的无损失可调用概念 template<typename Callable, typename T> concept LosslessInvocable = // 情况1:值传递,无窄化,类类型无切片 (std::invocable<Callable, T> && non_narrowing<T, std::decay_t<std::invoke_result_t<Callable, T>>> && (!std::is_class_v<T> || std::same_as<T, std::decay_t<std::invoke_result_t<Callable, T>>>)) || // 情况2:接受T的任意引用传递 invocable_with_reference_to<Callable, T> || // 情况3:接受T的基类的引用/指针(逆变,无切片) invocable_with_contravariant_base<Callable, T>;
修改后的callOnValue函数
template< typename Value, typename T = std::remove_reference_t<Value>::ValueType, typename Callable > requires LosslessInvocable<Callable, T> auto callOnValue(Value&& val, Callable&& callable) { return std::forward<Callable>(callable)(std::forward<Value>(val).value); }
验证效果
- 支持所有传递方式:
printI32、printI32Ref、printI32RefRef、printConstI32Ref均可正常调用; - 阻止窄化转换:
callOnValue(i, printI16)编译失败,callOnValue(i, printI64)编译通过; - 支持逆变:
callOnValue(d, printConstBaseRef)编译通过; - 阻止对象切片:
callOnValue(d, printBase)编译失败。
内容的提问来源于stack exchange,提问作者Nil Admirari
相关产品推荐
相关产品推荐

