Комментарии 10
Для себя нашел, что использовать template template для передачи метафункций не удобно, как минимум возникают проблемы с возвратом функций из функций. Вместо этого я в явном виде объявляю метафункцию аппликации:
А далее все функции имеют вложенный шаблон call:
Таким образом сами функции более не являются шаблоном от параметров, и ими можно оперировать как другими объектами — положить в список, вернуть в виде результата другой функции и т.п.
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 }}}
}
+2
Это прекрасно! Радует, что наследование удалось задействовать. С простым
typedef
рано или поздно приходится копипастить из-за того, что он остаётся там, где его определили. 0
С помощью небольшой магии можно сделать лямбды:
Которые можно применять на месте:
Польный пример
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>
Польный пример
0
Ваша идея мне очень нравится!
Скажите, а как ваш язык называется-то?
И можно ли из него вызывать метафункции, которые уже написаны на С++?
Скажите, а как ваш язык называется-то?
И можно ли из него вызывать метафункции, которые уже написаны на С++?
0
Осмысленного названия не придумал. (Поможете придумать?) Пока есть только рабочее «tpllang».
Вызывать метафункции можно, если написать их в том же стиле, в каком они появляются после трансляции. Каких-то запретов на использование неинициализированных сущностей нет, т.к. все они уже реализованы в компиляторах С++. Транслятор работает как препроцессор: писать можно что угодно, пока куски кода сливаются в корректный C++.
Вызывать метафункции можно, если написать их в том же стиле, в каком они появляются после трансляции. Каких-то запретов на использование неинициализированных сущностей нет, т.к. все они уже реализованы в компиляторах С++. Транслятор работает как препроцессор: писать можно что угодно, пока куски кода сливаются в корректный C++.
Вариант вставки через impure
Исходный код:
После трансляции в C++ (выводит «x»):
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();
}
0
Вот если бы какой-то вменяемый способ был бы Haskell код преобразовывать в с++ шаблоны…
0
Есть и подобные изыскания на этот счёт :)
Например: https://github.com/gergoerdi/metafun/
+2
НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре , чтобы оставить комментарий
Эзотерический язык, транслирующийся в шаблоны C++