4

I'm currently implementing a tiny metaprogramming-based compile-time computations library.

If have defined a base class for operators, which has a result typedef (I have decided to use integral wrappers like std::integral_constant as values instead of raw integral values, to provide an uniform interface along the library), and a n-ary operator base class, that checks if the operators has at least one operand:

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" );
};

So I defined alias for unary and binary operators:

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>;

That operator interfaces are used to define custom operators as alias, like comparison operators below:

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>>;

Now suppose we want to implement our custom equality operator for our own class. For example:

template<typename X , typename Y , typename Z>
struct vec3
{
    using x = X;
    using y = Y;
    using z = Z;
}; 

If the equality operator was made upon inheritance, instead of aliasing, this could be easily done through template specialization:

//Equality comparator implemented through inheritance:
template<typename LHS , typename RHS>
struct equal : public binary_operator<LHS,RHS,bool_wrapper<LHS::value == RHS::value>> {};

//Specialization 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>> {}; 

I know that template alias cannot be specialized.
My question is: Is there a way, that is not to use inheritance design instead of template aliases, to specialize this kind of template aliases?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Manu343726
  • 13,969
  • 4
  • 40
  • 75
  • You already have a solution. I don't understand what you seek (namely, this question doesn't explain what are the drawbacks from the existing solution that we need to avoid, and what are the goals that the existing solution fails to accomplish that are we are to strive for). – R. Martinho Fernandes Jul 23 '13 at 16:03
  • @R.MartinhoFernandes As I pointed out in the question, I know that it could be done through inheritance based dessign instead of template aliases. The question is only for curiosity. Really the inheritance way and the aliasing way are similar, but the fact that it cannot be done directly with template aliases strikes me. – Manu343726 Jul 23 '13 at 16:04
  • @Manu343726 you are looking for static inheritance, and this can probably be done using the Curiously Recursive Template Pattern. http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern. – IdeaHat Jul 23 '13 at 16:11
  • I think you are going to discover that `operator`, `nary_operator`, etc. are actually concepts instead of functions in your meta-language, and they will cause more problems than provide solutions. E.g., anything that acts like a binary operator *is* a binary operator whether or not it is defined in terms of `binary_operator<...>`. – Casey Jul 23 '13 at 16:18
  • @Casey I had already realized that it could be a problem. But, on the other hand, the prurpose of that concepts is not to provide a interface in the OOP sense. Im only trying to provide a simple way to implement custom operators. But its true, any template that has two type params, and has a `result` public typedef member, could act as a binary operator. But I think this is not a problem (I hope). – Manu343726 Jul 23 '13 at 16:36
  • For example, I see no advantage of `template using equal = binary_operator>;` over `template using equal = bool_wrapper;`. The second is even quicker to type! – Casey Jul 23 '13 at 17:08
  • @Casey well, its true. the first version its too verbose. And in fact, I dont use the operands in the operator template for anything. :( – Manu343726 Jul 23 '13 at 17:48
  • May be with a preprocessor macro you could achieve it simpler ? – jav974 Jul 25 '13 at 08:25
  • @jav974 I like to avoid the use of the CPP as much as possible. – Manu343726 Jul 25 '13 at 10:33

3 Answers3

5

The pattern I use to specialize template aliases (or provide recursive aliases) is to have a corresponding _impl struct. For example:

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>;

Users would have to specialize on my_alias_impl instead, but the rest of the public interface remains clean.

bstamour
  • 7,746
  • 1
  • 26
  • 39
  • What is the advantage of this approach? If you split the implementation from the interface, the alias is just a simple typedef, i.e. other name for the specialization. Why not just use the implementation, with no alias? – Manu343726 Jul 25 '13 at 10:36
  • 1
    My question is about template alias specialization. In your "solution", the use of template alias is absurd. Is just other name for the specialization, not a template alias (An alias with a template parameter). – Manu343726 Jul 25 '13 at 10:40
  • You asked for a best way (or workaround.) This is a workaround. I don't know what you plan on embedding inside your specializations, so I didn't bother adding ::type stuff, but I'm sure you can extrapolate from here. – bstamour Jul 28 '13 at 22:13
  • I had forgotten this question. But now I readed ypur answer again and I have noticed that is exactly what I'm doing. Thanks. I only have one suggest: That approach has a problem. If you are spezialicing functions (metafunctions), the user of that function expects that the function return the type of the result of the function. For example, a comparison function will return `std::integral_constant`. Using alias_impl to specialize that function results in the function alias returning `alias_impl`, not the constant type ... – Manu343726 Sep 22 '13 at 00:54
  • ... I had solutioned that using a function type, which wraps the result of the specialization. For example: `template struct function { using result = T; }; struct comparison_impl : public function> {}; using compare = typename comparison_impl::result;`. Here are examples about that: [Function declarations](https://github.com/Manu343726/Turbo/blob/master/operators.hpp) and [function implementations example](https://github.com/Manu343726/Turbo/blob/master/fixed_point.hpp) – Manu343726 Sep 22 '13 at 01:01
2

I know this is an old question, but it's a common problem and there's no satisfying answer in this thread up to now ... so I'll try to give one -- which addresses the general problem rather than the specific question (note that C++14 is required).

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"
}

DEMO

The code should be self-explaining: the main idea is to statically choose the type based on the number of arguments. The variadic declarations of A and B are necessary, otherwise the compiler complains that it can't instantiate a B with a single parameter or an A with two parameters, resp.

This approach is surely not completely general -- just think of default arguments or specializations of B -- but maybe it can be extended also to handle such situations. Nevertheless I found it sometimes useful in my coding.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
0

Type-alias cant be specialized but if your aim is to simplifiy traits syntactically then you can do it as I do. I combine related traits together in a blob which is anit-pattern but then I use inheritence on the blob itself instead of specialization to override a trait

#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
        ...
OOO
  • 914
  • 1
  • 10
  • 18