C++模板类中如何用编译期三元运算符获取对象成员?
解决eglue模板类行列数推导的编译错误问题
你的第一个方案之所以失败,核心原因是C++模板的实例化会检查所有分支的代码合法性,不管运行时条件是否成立。哪怕isEglueOrMat<T1>()是false,编译器还是会尝试解析f.n_rows,当T1是float这类没有n_rows成员的类型时,自然就报错了。
下面给你几种可行的解决方案,按C++版本从新到旧排序:
方案一:C++17及以上用constexpr if(最简洁)
利用C++17引入的编译期条件判断,让编译器只生成符合条件的分支代码,完全避免无效代码的编译检查。
首先写两个编译期辅助函数,用来推导行列数:
#include <type_traits> // 先修复你的type trait,确保value成员可用 template<typename> struct isEglueOrMat : std::false_type {}; template<typename T1, operations _op, typename T2> struct isEglueOrMat<eglue<T1, _op, T2>> : std::true_type {}; template<> struct isEglueOrMat<Matrix> : std::true_type {}; // 编译期推导n_rows template<typename T1, typename T2> constexpr unsigned determine_n_rows(const T1& f, const T2& s) { if constexpr (isEglueOrMat<T1>::value) { return f.n_rows; } else { // 静态断言确保另一个参数一定是合法类型 static_assert(isEglueOrMat<T2>::value, "One argument must be eglue or Matrix"); return s.n_rows; } } // 同理推导n_cols template<typename T1, typename T2> constexpr unsigned determine_n_cols(const T1& f, const T2& s) { if constexpr (isEglueOrMat<T1>::value) { return f.n_cols; } else { static_assert(isEglueOrMat<T2>::value, "One argument must be eglue or Matrix"); return s.n_cols; } }
然后修改eglue的构造函数:
template<typename T1, operations _op, typename T2> class eglue { public: const T1& First; const T2& Second; const unsigned n_rows; const unsigned n_cols; eglue(const T1& f, const T2& s) : First(f), Second(s), n_rows(determine_n_rows(f, s)), n_cols(determine_n_cols(f, s)) {} };
constexpr if会在编译期直接丢弃不符合条件的分支,所以当T1是float时,f.n_rows的代码根本不会被编译,完美解决错误。
方案二:C++11/14用SFINAE区分构造函数
如果你还在使用C++11或14,无法用constexpr if,可以通过SFINAE给构造函数加上互斥的编译条件,同时修复你之前的enable_if使用错误(你之前用了isEglueOrMat<T1>(),但需要的是isEglueOrMat<T1>::value这个bool常量):
template<typename T1, operations _op, typename T2> class eglue { public: const T1& First; const T2& Second; const unsigned n_rows; const unsigned n_cols; // 当T1是eglue/Matrix时,用T1的行列数 template<typename Dummy = void> eglue(const T1& f, const T2& s, std::enable_if_t<isEglueOrMat<T1>::value, Dummy>* = nullptr) : First(f), Second(s), n_rows(f.n_rows), n_cols(f.n_cols) {} // 当T1不是,但T2是eglue/Matrix时,用T2的行列数 template<typename Dummy = void> eglue(const T1& f, const T2& s, std::enable_if_t<!isEglueOrMat<T1>::value && isEglueOrMat<T2>::value, Dummy>* = nullptr) : First(f), Second(s), n_rows(s.n_rows), n_cols(s.n_cols) {} };
这里通过给构造函数添加一个默认的指针参数,让两个构造函数的签名在SFINAE生效后产生差异,避免重载冲突。同时用enable_if_t确保只有符合条件的构造函数会被实例化。
方案三:C++11/14用标签分发(Tag Dispatch)
另一种C++11/14的方案是标签分发,通过编译期生成的标签来选择对应的辅助函数:
// 定义两个标签类型 struct EglueOrMatTag {}; struct NonEglueOrMatTag {}; // 根据类型生成对应标签 template<typename T> constexpr auto get_type_tag() -> typename std::conditional< isEglueOrMat<T>::value, EglueOrMatTag, NonEglueOrMatTag >::type {} // 辅助函数:当第一个参数是合法类型时调用 template<typename T1, typename T2> unsigned get_n_rows(const T1& f, const T2&, EglueOrMatTag, ...) { return f.n_rows; } // 辅助函数:当第二个参数是合法类型时调用 template<typename T1, typename T2> unsigned get_n_rows(const T1&, const T2& s, NonEglueOrMatTag, EglueOrMatTag) { return s.n_rows; } // 同理实现get_n_cols... // 修改eglue构造函数 template<typename T1, operations _op, typename T2> class eglue { public: const T1& First; const T2& Second; const unsigned n_rows; const unsigned n_cols; eglue(const T1& f, const T2& s) : First(f), Second(s), n_rows(get_n_rows(f, s, get_type_tag<T1>(), get_type_tag<T2>())), n_cols(get_n_cols(f, s, get_type_tag<T1>(), get_type_tag<T2>())) {} };
标签分发的核心是让编译器根据参数的标签类型,自动选择匹配的辅助函数重载,同样能避免无效代码的编译。
内容的提问来源于stack exchange,提问作者Nabushika
相关产品推荐
相关产品推荐

