我目前正在实现一个基于元编程的小型编译时计算库。
如果已经为运算符定义了一个基类,那么它有一个结果typedef(我决定使用像std::integral_constant
这样的整数包装器作为值而不是原始的整数值,以便在库中提供统一的接口),以及一个n-ary运算符基类,用于检查运算符是否至少有一个操作数:
template<typename RESULT>
struct operator
{
using result = RESULT;
};
template<typename RESULT , typename... OPERANDS>
struct nary_operator : public operator<RESULT>
{
static_assert( sizeof... OPERANDS > 0 , "An operator must take at least one operand" );
};
所以我为一元和二元运算符定义了别名:
template<typename OP , typename RESULT>
using unary_operator = nary_operator<RESULT , OP>;
template<typename LHS , typename RHS , typename RESULT>
using binary_operator = nary_operator<RESULT , LHS , RHS>;
运算符接口用于将自定义运算符定义为别名,如下面的比较运算符:
template<typename LHS , typename RHS>
using equal = binary_operator<LHS,RHS,bool_wrapper<LHS::value == RHS::value>>;
template<typename LHS , typename RHS>
using not_equal = logical_not<equal<LHS,RHS>>;
template<typename LHS , typename RHS>
using less_than = binary_operator<LHS,RHS,bool_wrapper<LHS::value < RHS::value>>;
template<typename LHS , typename RHS>
using bigger_than = less_than<RHS,LHS>;
template<typename LHS , typename RHS>
using less_or_equal = logical_not<bigger_than<LHS,RHS>>;
template<typename LHS , typename RHS>
using bigger_or_equal = logical_not<less_than<LHS,RHS>>;
现在假设我们想为自己的类实现自定义相等运算符。例如:
template<typename X , typename Y , typename Z>
struct vec3
{
using x = X;
using y = Y;
using z = Z;
};
如果相等运算符是通过继承进行的,而不是别名,则可以通过模板专门轻松完成:
//Equaity comparator implemented through inheritance:
template<typename LHS , typename RHS>
struct equal : public binary_operator<LHS,RHS,bool_wrapper<LHS::value == RHS::value>> {};
//Specialitation of the operator for vec3:
template<typename X1 , typename Y1 , typename Z1 , typename X2 , typename Y2 , typename Z2>
struct equal<vec3<X1,Y1,Z1>,vec3<X2,Y2,Z2>> : public binary_operator<vec3<X1,Y1,Z1>,vec3<X2,Y2,Z2> , bool_wrapper<X1 == X2 && Y1 == Y2 && Z1 == Z2>> {};
我知道template alias cannot be specialized 我的问题是:有没有办法,不使用继承dessign而不是模板别名来专门化这种模板别名?
答案 0 :(得分:5)
我用来专门化模板别名(或提供递归别名)的模式是拥有一个相应的_impl结构。例如:
template <typename T>
struct my_alias_impl { /* def'n */ };
template <>
struct my_alias_impl<int> { /* alternate def'n */ };
template <typename T>
using my_alias = my_alias_impl<T>;
用户必须专注于my_alias_impl,但公共接口的其余部分仍然保持干净。
答案 1 :(得分:2)
我知道这是一个老问题,但这是一个常见的问题,到目前为止这个帖子中没有令人满意的答案......所以我会试着给出一个 - 解决一般问题而不是具体问题(注意需要C ++ 14)。
namespace impl
{
template<typename ... Args> struct A;
template<typename T> struct A<T>
{
A() {std::cout<<"I'm an A"<<std::endl;}
};
template<typename ... Args> struct B;
template<typename T, typename V> struct B<T, V>
{
B() {std::cout<<"I'm a B"<<std::endl;}
};
}
template<typename ... Args>
using C = std::conditional_t<sizeof...(Args)==1
, typename impl::A<Args ...>
, typename impl::B<Args ...> >;
int main()
{
C<double> a; //prints "I'm an A"
C<double, int> b; //prints "I'm a B"
}
代码应该是自我解释的:主要思想是根据参数的数量静态选择类型。 A
和B
的可变参数是必要的,否则编译器会抱怨它不能使用单个参数实例化B
或使用两个参数实例化A
,resp。
这种方法肯定不是完全一般的 - 只考虑B
的默认参数或特化 - 但也许它也可以扩展到处理这种情况。不过我发现它在我的编码中有时很有用。
答案 2 :(得分:0)
Type-alias不能专门化,但如果你的目标是在语法上简化特性,那么你可以像我一样做。我将相关特征组合在一个blob中,这是一个anit-pattern,但后来我在blob本身使用了继承而不是专门化来覆盖一个特征
#define DEFINE_TRAIT(TraitClass,TraitName) \
template<typename... T> using TraitName = typename TraitClass<T...>::TraitName
DEFINE_TRAIT(BinaryOpTrait, equal);
DEFINE_TRAIT(BinaryOpTrait, not_equal);
...
...
template<typename LHS , typename RHS>
struct BinaryOpTraitBase
{
using equal = binary_operator<LHS,RHS,bool_wrapper<LHS::value == RHS::value>>;
using not_equal = logical_not<::equal<LHS,RHS>>;
...
...
};
template<typename LHS , typename RHS>
struct BinaryOpTrait : BinaryOpTraitBase <LHS, RHS> {};
typename<>
struct BinaryOpTrait<vec3, vec3> : BinaryOpTraitBase<vec3, vec3>
{
using equal = /* custom op */
};
//Usage:
if(equal<int,int>(1,1))
...
if(equal<vec3,vec3>(v1,v1) //uses specialized
...