Sebastian
Sebastian

Reputation: 795

Writing typeclasses in C++

I'm trying to write something similar in C++ to a set of typeclasses, and I'm struggling with how to arrange the template signatures, or if it's even possible to do what I want to do.

To break it down to its smallest example, say I have this:

template<typename S, typename T>
struct Homomorphism {
    //Defined in specialization: static const T morph(const S&);
    static constexpr bool is_instance = false;
    using src  = S;
    using dest = T;
};

template<typename S, typename T>
struct Monomorphism : Homomorphism<S, T> {
    //Defined in specialization: static const T morph(const &S);
    static constexpr bool is_instance = false;
    using src  = S;
    using dest = T;
};

I have specializations of these classes (and other morphisms) for data types in my program.

What I'd like to do now is to write a struct template that will take two homomorphisms or two monomorphisms and compose them to generate a new homomorphism or monomorphism struct respectively, i.e. something like:

template<typename S, typename T, typename U,
         typename HST = Homomorphism<S, T>,
         typename HTU = Homomorphism<T, U>,
         typename HSU = Homomorphism<S, U> >
struct CompositionMorphism : HSU {
    static const U morph(const S &s) {
        return HTU::morph(HST::morph(s));
    }
    static constexpr bool is_instance = true;
    using src  = S;
    using dest = U;
}

This actually worked for composing specialized instances of Homomorphism via:

CompositionMorphism<Class1, Class2, Class3>::morph(class1Instance);

when I had:

struct Homomorphism<Class1, Class2> {
    static const Class2 morph(const Class1 &c) {
        ...
    }
};

and analogous for Homomorphism<Class2, Class3>.

Now, however, I'd like to write:

template<typename S, typename T, typename U,
        typename MST = Monomorphism<S, T>,
        typename MTU = Monomorphism<T, U>,
        typename MSU = Monomorphism<S, U> >
struct CompositionMorphism : MSU {
    static const U morph(const S &s) {
        return MTU::morph(MST::morph(s));
    }
    static constexpr bool is_instance = true;
    using src  = S;
    using dest = U;
};

but the compiler is, unsurprisingly complaining about a duplicate definition of CompositionMorphism.

Is there a way to write CompositionMorphism and its specializations with Homomorphism and Monomorphism so that I will be able to do things like call:

template<> struct Homomorphism<Class1, Class2> { ... };
template<> struct Homomorphism<Class2, Class3> { ... };
CompositionMorphism<Class1, Class2, Class3>::morph(c1Instance);

or:

template<> struct Monomorphism<Class1, Class2> { ... };
template<> struct Monomorphism<Class2, Class3> { ... };
CompositionMorphism<Class1, Class2, Class3>::morph(c1Instance);

or:

template<> struct Monomorphism<Class1, Class2> { ... };
template<> struct Homomorphism<Class2, Class3> { ... };
CompositionMorphism<Class1, Class2, Class3>::morph(c1Instance);

and have the compiler pick the closest CompositionMorphism specialization based on my morphism hierarchy?

Upvotes: 0

Views: 1852

Answers (3)

kiloalphaindia
kiloalphaindia

Reputation: 561

OK, Sometimes i need a little more thinking, but this is probably what you are looking for:

#include <type_traits>
#include <cstdint>
#include <tuple>

template<typename S, typename T>
struct Homomorphism;

template<typename S, typename T>
struct Monomorphism;

class Class1{};
class Class2{};
class Class3{};

template<> struct Homomorphism<Class1, Class2> 
{ 
     static const Class2 morph(const Class1&); 
     static constexpr bool is_instance = true;#
};

template<> struct Homomorphism<Class2, Class3> 
{
    static const Class3 morph(const Class2&);
    static constexpr bool is_instance = true;
};

template<typename S, typename T>
struct Homomorphism {
    //Defined in specialization: static const T morph(const S&);
    static constexpr bool is_instance = false;
    using src  = S;
    using dest = T;
};

template<typename S, typename T>
struct Monomorphism : Homomorphism<S, T> {
    //Defined in specialization: static const T morph(const &S);
    static constexpr bool is_instance = false;
    using src  = S;
    using dest = T;
};


namespace details {
    template<typename T, typename U, std::enable_if_t<Homomorphism<T,U>::is_instance>* = nullptr>
    U morph (const T& t)
    {return  Homomorphism<T,U>::morph(t);}

    template<typename T, typename U,  std::enable_if_t<Monomorphism<T,U>::is_instance>* = nullptr>
    U morph (const T& t)
    {return  Monomorphism<T,U>::morph(t);}


 }

template <typename S, typename T, typename U>
class CompositionMorphism
{
public:
    static U morph (const S& s)  {return  details::morph<T,U>(details::morph<S,T>(s));}
    static constexpr bool is_instance = true;
};


 int main(int, char**)
{
    Class1 c1Instance;
    CompositionMorphism<Class1, Class2, Class3>::morph(c1Instance);
    std::ignore = d;
}

And you might possibly want to create composed Homo/Mono morphism manually as follows:

template <> class Monomorphism<Class1,Class3> : public CompositionMorphism<Class1, Class2, Class3> {};

Then they can be reused by CompositionMorphism automatically.

Upvotes: 1

super
super

Reputation: 12928

You could try something like writing a template to select Homomorphism or Monomorphism based on SFINAE on the morph function.

template <typename S, typename T, typename = void>
struct SelectMorphism {
    using type = Homomorphism<S, T>;
};

template <typename S, typename T>
struct SelectMorphism<S, T, std::enable_if_t<std::is_same_v<decltype(Monomorphism<S, T>::morph(std::declval<S>())), const T>>> {
    using type = Monomorphism<S, T>;
};

This will check if Monomorphism<S, T>::morph(S) would return a T, if so select a Monomorphism<S, T>. If not SFINAE will fail and default to a Homomorphism<S, T>.

Then we change CompositionMorphism to use this template like so

template<typename S, typename T, typename U,
         typename HST = typename SelectMorphism<S, T>::type,
         typename HTU = typename SelectMorphism<T, U>::type,
         typename HSU = typename SelectMorphism<S, U>::type >
struct CompositionMorphism : HSU {
    static const U morph(const S &s) {
        return HTU::morph(HST::morph(s));
    }
    static constexpr bool is_instance = true;
    using src  = S;
    using dest = U;
};

You can see a live demo here of this full working example. It requires c++17 but can be written for c++11 as well (slightly more verbose).

#include <iostream>

template<typename S, typename T>
struct Homomorphism {
    //Defined in specialization: static const T morph(const S&);
    static constexpr bool is_instance = false;
    using src  = S;
    using dest = T;
};

template<typename S, typename T>
struct Monomorphism : Homomorphism<S, T> {
    //Defined in specialization: static const T morph(const &S);
    static constexpr bool is_instance = false;
    using src  = S;
    using dest = T;
};

template <typename S, typename T, typename = void>
struct SelectMorphism {
    using type = Homomorphism<S, T>;
};

template <typename S, typename T>
struct SelectMorphism<S, T, std::enable_if_t<std::is_same_v<decltype(Monomorphism<S, T>::morph(std::declval<S>())), const T>>> {
    using type = Monomorphism<S, T>;
};

struct Class1 {};

struct Class2 {};

struct Class3 {};

template<>
struct Monomorphism<Class1, Class2> : Homomorphism<Class1, Class2> {
    static const Class2 morph(const Class1&) { std::cout << "Morphing in Mono<Class1, Class2>" << std::endl; return Class2{}; }
    static constexpr bool is_instance = false;
    using src  = Class1;
    using dest = Class2;
};

template<>
struct Homomorphism<Class2, Class3> {
    static const Class3 morph(const Class2&) { std::cout << "Morphing in Homo<Class2, Class3>" << std::endl; return Class3{}; }
    static constexpr bool is_instance = false;
    using src  = Class2;
    using dest = Class3;
};

template<typename S, typename T, typename U,
         typename HST = typename SelectMorphism<S, T>::type,
         typename HTU = typename SelectMorphism<T, U>::type,
         typename HSU = typename SelectMorphism<S, U>::type >
struct CompositionMorphism : HSU {
    static const U morph(const S &s) {
        return HTU::morph(HST::morph(s));
    }
    static constexpr bool is_instance = true;
    using src  = S;
    using dest = U;
};

int main ()
{
    CompositionMorphism<Class1, Class2, Class3>::morph(Class1{});
}

Upvotes: 1

max66
max66

Reputation: 66190

As observed by Super, if you pass only T, U and V the compiler doesn't know if is the case to chose Homomorphism or Monomorphism.

So I suppose you should pass Homomorphism<T, U> and Homomorphism<U, V> (Homomorphism<T, V> can be constructed) or Monomorphism<T, U> and Monomorphism<U, V>

If you want impose two Homomorphism or two Monomorphism (I mean: if you want exclude a Monomorphism toghether with a Homomorphism) you can write something as follows

template <typename, typename>
struct CompositionMorphism;

template <template <typename, typename> class C,
          typename S, typename T, typename U>
struct CompositionMorphism<C<S, T>, C<T, U>>
 {
   using comp = C<S, U>;

   static const U morph (const S & s)
    { return C<T, U>::morph(C<S, T>::morph(s)); }
 };

and call it as follows

   Homomorphism<int, long>        h0;
   Homomorphism<long, long long>  h1;
   Monomorphism<int, long>        m0;
   Monomorphism<long, long long>  m1;

   CompositionMorphism<decltype(h0), decltype(h1)>  h2;
   CompositionMorphism<decltype(m0), decltype(m1)>  m2;

   // compiler error
   //CompositionMorphism<decltype(h0), decltype(m1)>  hm;

The following is a full compiling example

#include <array>
#include <iostream>

template <typename S, typename T>
struct Homomorphism
 {
   //Defined in specialization: static const T morph(const S&);
   static constexpr bool is_instance = false;
   using src  = S;
   using dest = T;
 };

template <typename S, typename T>
struct Monomorphism : Homomorphism<S, T>
 {
   //Defined in specialization: static const T morph(const &S);
   static constexpr bool is_instance = false;
   using src  = S;
   using dest = T;
 };

template <typename, typename>
struct CompositionMorphism;

template <template <typename, typename> class C,
          typename S, typename T, typename U>
struct CompositionMorphism<C<S, T>, C<T, U>>
 {
   using comp = C<S, U>;

   static const U morph (const S & s)
    { return C<T, U>::morph(C<S, T>::morph(s)); }
 };


int main ()
 { 
   Homomorphism<int, long>        h0;
   Homomorphism<long, long long>  h1;
   Monomorphism<int, long>        m0;
   Monomorphism<long, long long>  m1;

   CompositionMorphism<decltype(h0), decltype(h1)>  h2;
   CompositionMorphism<decltype(m0), decltype(m1)>  m2;

   // compiler error
   //CompositionMorphism<decltype(h0), decltype(m1)>  hm;

   static_assert( std::is_same<Homomorphism<int, long long>,
                               decltype(h2)::comp>{}, "!" );

   static_assert( std::is_same<Monomorphism<int, long long>,
                               decltype(m2)::comp>{}, "!" );
 }

Upvotes: 1

Related Questions