Как стать автором
Обновить

Комментарии 10

Для себя нашел, что использовать template template для передачи метафункций не удобно, как минимум возникают проблемы с возвратом функций из функций. Вместо этого я в явном виде объявляю метафункцию аппликации:
template <class F, class... X>
using apply = typename F::template call<X...>::value;

А далее все функции имеют вложенный шаблон call:
struct And {
    template <class X, class... Y>
    struct call;
};

template <class X>
struct And::call<X> {
    using value = X;
};

template <class X, class... Y>
struct And::call<Zero, X, Y...> {
    using value = Zero;
};

template <class X, class... Y>
struct And::call<One, X, Y...> {
    using value = apply<And, X, Y...>;
};

Таким образом сами функции более не являются шаблоном от параметров, и ими можно оперировать как другими объектами — положить в список, вернуть в виде результата другой функции и т.п.

Код
#include <iostream>

template <class F, class... X>
using apply = typename F::template call<X...>::value;

template <class T>
struct Identity {
    template <class...>
    struct call {
        using value = T;
    };
};

struct One : Identity<One> {
};

struct Zero : Identity<Zero> {
};

struct Nil : Identity<Nil> {
};

struct Not {
    template <class X>
    struct call;
};

template <>
struct Not::call<Zero> {
    using value = One;
};

template <>
struct Not::call<One> {
    using value = Zero;
};

struct And {
    template <class X, class... Y>
    struct call;
};

template <class X>
struct And::call<X> {
    using value = X;
};

template <class X, class... Y>
struct And::call<Zero, X, Y...> {
    using value = Zero;
};

template <class X, class... Y>
struct And::call<One, X, Y...> {
    using value = apply<And, X, Y...>;
};


template <class H, class T>
struct Cons : Identity<Cons<H, T>> {
};

struct Head {
    template <class>
    struct call;
};

template <class H, class T>
struct Head::call<Cons<H, T>> {
    using value = H;
};

struct Tail {
    template <class>
    struct call;
};

template <class H, class T>
struct Tail::call<Cons<H, T>> {
    using value = T;
};

struct List {
    template <class...>
    struct call;
};

template <>
struct List::call<> {
    using value = Nil;
};

template <class H, class... T>
struct List::call<H, T...> {
    using value = Cons<H, apply<List, T...>>;
};

struct Map {
    template <class T, class F>
    struct call;
};

template <class H, class T, class F>
struct Map::call<Cons<H, T>, F> {
    using value = Cons<apply<F, H>, apply<Map, T, F>>;
};

template <class F>
struct Map::call<Nil, F> {
    using value = Nil;
};


std::ostream& operator<<( std::ostream& os, One )
{
    os << 1;
    return os;
}

std::ostream& operator<<( std::ostream& os, Zero )
{
    os << 0;
    return os;
}

std::ostream& operator<<( std::ostream& os, Nil )
{
    return os;
}

std::ostream& operator<<( std::ostream& os, And )
{
    os << "&&";
    return os;
}

template <class H, class T>
std::ostream& operator<<( std::ostream& os, Cons<H, T> )
{
    os << "{ " << H() << " " << T() << "}";
    return os;
}

int main()
{
    std::cout << One() << std::endl;
    // 1

    std::cout << apply<And, One, One, One, One>() << std::endl;
    // 1

    std::cout << apply<And, One, One, Zero, One>() << std::endl;
    // 0

    using list = apply<List, One, One, Zero, One>;

    std::cout << list() << std::endl;
    // { 1 { 1 { 0 { 1 }}}}

    std::cout << apply<Map, list, Not>() << std::endl;
    //{ 0 { 0 { 1 { 0 }}}}

    std::cout << apply<Map, list, One>() << std::endl;
    //{ 1 { 1 { 1 { 1 }}}}

    std::cout << apply<Map, list, Zero>() << std::endl;
    //{ 0 { 0 { 0 { 0 }}}}


    using list2 = apply<List, list, list>;

    std::cout << apply<Map, list2, Tail>() << std::endl;
    //{ { 1 { 0 { 1 }}} { { 1 { 0 { 1 }}} }}


    std::cout << apply<Map, list2, One>() << std::endl;
    //{ 1 { 1 }}

    std::cout << apply<List, One, And, Zero>() << std::endl;
    //{ 1 { && { 0 }}}
}

Это прекрасно! Радует, что наследование удалось задействовать. С простым typedef рано или поздно приходится копипастить из-за того, что он остаётся там, где его определили.
С помощью небольшой магии можно сделать лямбды:
template <class T>
struct Eval {
    template <class...>
    struct call {
        using value = T;
    };
};

template <class... T>
struct Apply {
    template <class...>
    struct call;
};

template <class F, class... X>
struct Eval<Apply<F, X...>> {
    template <class...>
    struct call {
        using value = apply<apply<Eval<F>>, apply<Eval<X>>...>;
    };
};

template <class... T>
template <class...>
struct Apply<T...>::call {
    using value = apply<Eval<Apply<T...>>>;
};

template <class F, class X>
struct Substitute {
    template <class...>
    struct call {
        using value = F;
    };
};

template <class X>
struct Substitute<X, X> {
    template <class Y>
    struct call {
        using value = Y;
    };
};

template <template <class...> class F, class... T, class X>
struct Substitute<F<T...>, X> {
    template <class Y>
    struct call {
        using value = F<apply<Substitute<T, X>, Y>...>;
    };
};

template <class X, class F>
struct Lambda {
    template <class Y>
    struct call {
        using value = apply<Eval<apply<Substitute<F, X>, Y>>>;
    };
};

template <class X, class F>
struct Eval<Lambda<X, F>> {
    template <class...>
    struct call {
        using value = Lambda<X, F>;
    };
};

Которые можно применять на месте:

struct X{};
using lambda = Lambda<X, Apply<List, X>>;
using result = apply<lambda, One>; // Cons<One, Nil>


Польный пример
Ваша идея мне очень нравится!
Скажите, а как ваш язык называется-то?

И можно ли из него вызывать метафункции, которые уже написаны на С++?
Осмысленного названия не придумал. (Поможете придумать?) Пока есть только рабочее «tpllang».

Вызывать метафункции можно, если написать их в том же стиле, в каком они появляются после трансляции. Каких-то запретов на использование неинициализированных сущностей нет, т.к. все они уже реализованы в компиляторах С++. Транслятор работает как препроцессор: писать можно что угодно, пока куски кода сливаются в корректный C++.
Вариант вставки через impure
Исходный код:
impure {
  #include <iostream>

  template <typename T>
  struct id {
    typedef T _value;
  };
}

x = impure {
  x() { std::cout << "x"; }
}

y = id(x);

impure {
  int main() {
    impure y();
  }
}

После трансляции в C++ (выводит «x»):
#include <iostream>

template <typename T>
struct id {
  typedef T _value;
};
struct x {
  x() { std::cout << "x"; }
};
typedef typename id <x> ::_value y;
int main() {
  y();
}

Я предложил бы вам устроить голосование по поводу названия. Но вообще ему ведь необязательно быть осмысленным, достаточно быть просто красивым и благозвучным. Как «Оберон», например.
Вот если бы какой-то вменяемый способ был бы Haskell код преобразовывать в с++ шаблоны…
Вроде не выглядит, как что-то серьёзное.
НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории