编译时推断C++方法内部读取成员的实现方案问询
问题背景
考虑以下代码:
#include <type_traits> #include <tuple> // 遵循该模式的任意结构体 struct X { int x; using Type = int; }; struct Y { float y; using Type = float; }; struct Z { const char* z; using Type = const char*; }; struct W { double w; using Type = double; }; // 通过继承组合的结构体 struct S : X, Y, Z, W { // 仅读取`x`的方法 Y::Type MethodY() const { // 示例逻辑 return static_cast<float>(x) * 10.0F; } // 读取`x`、`y`和`z`的方法 W::Type MethodW() const { // 示例逻辑 return z ? static_cast<double>(y) : static_cast<double>(x); } // 可扩展更多类似方法 // ... }; template<auto Method> using WhatMethodReads = std::tuple</* 方法`Method`所依赖的`S`的基类列表 */>; // 预期目标: static_assert(std::is_same_v<WhatMethodReads<&S::MethodY>, std::tuple<X>>); static_assert(std::is_same_v<WhatMethodReads<&S::MethodW>, std::tuple<X, Y, Z>>); int main() { }
核心问题:给定一个方法,需要在编译时推断其内部读取了哪些特定成员。本示例采用多继承方式,每个成员绑定到专属结构体;也欢迎支持其他组合方式的解决方案,方法也可为接收S常量引用的自由函数。
已尝试方案
尝试通过模板元编程修改继承权限(改为私有隐藏成员)或移除基类,测试方法是否可编译,期望SFINAE在方法无法编译时选择特化分支:例如移除基类Y后,MethodW非法则说明依赖Y,需加入结果元组;MethodY仍合法则不加入。但实际编译器直接报错而非触发SFINAE。
当前核心需求:实现合法的constexpr布尔值判断函数体是否合法,以此解决问题。以下是尝试的示例代码及错误:
#include <type_traits> #include <iostream> template<class T> auto Function() { // 假设逻辑更复杂,`requires { T + T; }`不足以满足需求,需基于函数体合法性触发替换失败 return T{} + T{}; } template<class T> struct IsFunctionLegal : std::false_type { }; template<class T> // 尝试通过`auto`返回类型结合`decltype`推断函数体合法性,但触发编译错误而非SFINAE requires requires { std::is_same_v<decltype(Function<T>()), T>; } struct IsFunctionLegal<T> : std::true_type { }; int main() { std::cout << IsFunctionLegal<int>::value; // 正常输出true std::cout << IsFunctionLegal<void>::value; // 报错而非输出false }
错误信息:
<source>: In instantiation of 'auto Function() [with T = void]': <source>:25:60: required by substitution of 'template<class T> requires requires{is_same_v<decltype((Function<T>)()), T>;} struct IsFunctionLegal<T> [with T = void]' <source>:32:39: required from here <source>:12:16: error: invalid operands of types 'void' and 'void' to binary 'operator+' 12 | return T{} + T{}; | ~~~~^~~~~ Build failed
解决方案
1. 修复函数体合法性判断的SFINAE问题
原代码的问题在于Function<T>的实例化会直接触发硬错误,而非在模板参数替换阶段失败。我们可以用std::void_t结合decltype包裹表达式,让非法表达式导致模板特化替换失败,从而触发SFINAE:
#include <type_traits> #include <iostream> // 简单表达式的合法性判断 template<class T, class = void> struct IsFunctionLegal : std::false_type {}; template<class T> struct IsFunctionLegal<T, std::void_t<decltype(T{} + T{})>> : std::true_type {}; // 复杂逻辑的合法性判断(用lambda包装逻辑) template<class T, class = void> struct IsComplexFunctionLegal : std::false_type {}; template<class T> struct IsComplexFunctionLegal<T, std::void_t< decltype([]() -> T { // 这里可以放置任意复杂逻辑 auto a = T{}; auto b = T{}; return a + b; }()) >> : std::true_type {}; int main() { std::cout << IsFunctionLegal<int>::value << '\n'; // 输出1 std::cout << IsFunctionLegal<void>::value << '\n'; // 输出0 std::cout << IsComplexFunctionLegal<int>::value << '\n'; // 输出1 std::cout << IsComplexFunctionLegal<void>::value << '\n'; // 输出0 }
2. 编译时推断方法依赖的基类
核心思路是:对每个候选基类,生成移除该基类的S变体,检查方法在该变体上是否可调用。若不可调用,则说明方法依赖该基类,最终收集所有依赖基类到元组中:
#include <type_traits> #include <tuple> #include <utility> // 基础结构体定义 struct X { int x; using Type = int; }; struct Y { float y; using Type = float; }; struct Z { const char* z; using Type = const char*; }; struct W { double w; using Type = double; }; // 原始组合结构体 struct S : X, Y, Z, W { Y::Type MethodY() const { return static_cast<float>(x) * 10.0F; } W::Type MethodW() const { return z ? static_cast<double>(y) : static_cast<double>(x); } }; // 辅助工具:从基类列表中排除指定类型 template<class Exclude, class... Bases> struct ExcludeBase; template<class Exclude> struct ExcludeBase<Exclude> { using type = std::tuple<>; }; template<class Exclude, class First, class... Rest> struct ExcludeBase<Exclude, First, Rest...> { using type = std::conditional_t< std::is_same_v<Exclude, First>, typename ExcludeBase<Exclude, Rest...>::type, decltype(std::tuple_cat(std::tuple<First>{}, typename ExcludeBase<Exclude, Rest...>::type{})) >; }; template<class Exclude, class... Bases> using ExcludeBase_t = typename ExcludeBase<Exclude, Bases...>::type; // 生成移除指定基类的S变体 template<class... Bases> struct SVariant : Bases... {}; template<class Exclude> using SWithout = SVariant< std::tuple_element_t<0, ExcludeBase_t<Exclude, X, Y, Z, W>>, std::tuple_element_t<1, ExcludeBase_t<Exclude, X, Y, Z, W>>, std::tuple_element_t<2, ExcludeBase_t<Exclude, X, Y, Z, W>> >; // 判断方法是否依赖指定基类 template<auto Method, class Base> struct DependsOnBase : std::false_type {}; template<auto Method, class Base> requires requires(const SWithout<Base>& s) { (s.*Method)(); } struct DependsOnBase<Method, Base> : std::false_type {}; template<auto Method, class Base> requires (!requires(const SWithout<Base>& s) { (s.*Method)(); }) struct DependsOnBase<Method, Base> : std::true_type {}; // 收集所有依赖的基类到元组 template<auto Method, class... Bases> struct CollectDependencies { using type = decltype(std::tuple_cat( std::conditional_t<DependsOnBase<Method, Bases>::value, std::tuple<Bases>, std::tuple<>>{}... )); }; template<auto Method> using WhatMethodReads = typename CollectDependencies<Method, X, Y, Z, W>::type; // 验证预期目标 static_assert(std::is_same_v<WhatMethodReads<&S::MethodY>, std::tuple<X>>); static_assert(std::is_same_v<WhatMethodReads<&S::MethodW>, std::tuple<X, Y, Z>>); int main() { }
方案说明
ExcludeBase:用于从基类列表中排除指定类型,生成剩余基类的元组。SWithout<Exclude>:继承自排除指定基类后的剩余基类,模拟移除该基类的场景。DependsOnBase:通过检查方法在SWithout<Base>上的可调用性,判断方法是否依赖该基类。CollectDependencies:遍历所有候选基类,将依赖的基类收集到元组中,最终生成WhatMethodReads的类型。
内容的提问来源于stack exchange,提问作者bigmazi
相关产品推荐
相关产品推荐

