Здравствуйте. Если придумали такие замечательные функциональные постфиксы как delete, default, final, override и т.п., то почему бы не нагрузить популярный сейчас auto новой функциональностью?
Помеченная таким словом функция — это автоматический шаблон, параметризируемый "верхним" типом объекта, для которого генерируется. Проще говоря, это функция, которая автоматически генерируется для каждого класса-потомка.
Такая функция вполне может быть виртуальной, так как ее сигнатура не меняется от предка к наследнику. Она может быть и настоящим шаблоном, но понятно, что не-виртуальным.
struct X
{
virtual X* clone() const auto
{
return new decltype(*this)(*this);
}
};
struct Y : X
{
};
int main()
{
X* y = new Y;
X* y2 = y->clone();
}
А можно и автоматические переменные, почему бы и нет?
struct X
{
X() auto
{
s_objects.insert(this);
}
~X() auto
{
s_objects.erase(this);
}
static std::set<auto*> s_objects;
}
Для чего это может быть полезно? В основном, для всяких автоматизаций, простейшей авторефлексии (все потомки класса X умеют себя клонировать, сравнивать, регистрировать в фабриках и т.п.)
Здравствуйте, Went, Вы писали:
W> почему бы не нагрузить популярный сейчас auto новой функциональностью?
In C++1z you just write "auto auto(auto auto) { auto; }". The compiler infers the rest from context.
Здравствуйте, Кодт, Вы писали:
К>Но всё равно, дженерики и тайпклассы из коробки — было бы круто.
есть идея поискать другую коробку
Здравствуйте, Went, Вы писали:
W>Здравствуйте, Кодт, Вы писали:
К>>Да вообще, пора уже наряду с шаблонами завести дженерики.
К>>А заодно — ко-контра-вариантность, тайпклассы (рантайм-версия трейтсов), и не замахнуться ли вообще на Хиндли нашего Миллера? </troll>
К>>Но всё равно, дженерики и тайпклассы из коробки — было бы круто.
W>Чувствую себя идиотом. Где можно почитать про все эти страшные слова?
Не претендуя на полноту и предельную точность...
Дженерики — в яве и дотнете.
Это почти те же шаблоны, с той разницей, что для каждого набора параметров не генерируется новый код.
Очень грубо говоря,
// реализация дженерика
struct foo_base
{
void* x_; // sizeof(x_) не зависит от типа
void* bar_() { return x_; }
virtual void buz_(void* z) { x_ = z; }
};
// типобезопасный фасад дженерика
template<class T> struct foo : foo_base
{
T* bar() { return (T*)bar_(); }
void buz(T* z) { buz_(z); }
};
Ковариантность и контравариантность — обобщение на функции и контейнеры (указатели, массивы, потоки) принципа подстановки Лисков.
См. яву сотоварищи.
В С++ оно ярко проявляется с указателями на члены.
struct Foo {};
struct Bar : Foo {};
// немножко реинтерпрет-кастов...
// ковариантность
Bar* make_bar();
Foo* (*make_some_foo)() = ((Foo*)(*)()) make_bar; // так делать можно, потому что make_bar возвращает какой-то субкласс Foo и следует LSP
// контравариантность
void take_foo(Foo*);
void (*take_some_bar)(Bar*) = (void(*)(Bar*)) take_foo; // так тоже можно сделать, потому что take_foo является частным случаем take_some_bar
// а вот и их честные аналоги безо всяких хаков
Bar* point_to_bar;
Foo* point_to_some_foo = point_to_bar; // наследника можно привести к предку
void Foo::the_method_of_foo();
void (Bar::*some_method_of_bar)() = &Foo::the_method_of_foo; // у наследника можно вызвать метод предка, поэтому метод предка приводится к наследнику
Ну и с контейнерами (здесь — контейнер одиночного элемента, сиречь, указатель)
struct Foo { void f(); };
struct Bar : Foo { void g(); };
struct Buz : Bar { voig h(); };
Foo* foo = new Foo();
Bar* bar = new Bar();
Buz* buz = new Buz();
// ковариантность по in-параметру
void take_bar(Bar* x) { x->g(); }
take_bar((Bar*)foo); // это запрещено, в т.ч. системой типов С++, иначе будет foo->g()
take_bar( bar);
take_bar( buz); // LSP в чистом виде
// контравариантность по out-параметру
void give_bar(Bar*& x); { x = rand() ? new Bar() : new Buz(); }
give_bar((Bar*&)foo); // foo может быть и Bar, и Buz (но С++ требует хакнуть тип)
give_bar( bar); // bar тоже
give_bar((Bar*&)buz); // а вот это запрещено! иначе buz = new Bar
// инвариантность по inout-параметру
void edit_bar(Bar*& x) { take_bar(x); give_bar(x); }
edit_bar((Bar*&)foo); // нельзя, т.к. foo->g()
edit_bar( bar);
edit_bar((Bar*&)buz); // нельзя, т.к. buz = new Bar
Система типов Хиндли-Милнера (она же система F) — см. типизированное лямбда-исчисление.
В С++ ради перегрузки сделан односторонний вывод типов: по аргументам находится подходящая сигнатура функции, оттуда берётся тип результата, и так далее до самых внешних скобок. По типу результата восстановить типы аргументов не всегда возможно.
Та же история с зависимыми именами (типы и члены классов). Из параметров шаблона можно получить зависимый тип, а из зависимого типа параметры шаблона — нет.
Хотя ИНОГДА эти уравнения и однозначны. Но авторы стандарта, а за ними и разработчики компиляторов, приняли решение не морочить себе голову никогда.
В системе F эти уравнения однозначны всегда. Поэтому перегрузки функций должны иметь однообразные сигнатуры.
Если, скажем, есть оператор сдвига int operator<<(int,int), то его можно обобщить до T operator<<(T,T), и туда уже не впишется ostream& operator<<(ostream&,чтопопало).
Тайпклассы (typeclass) — см. язык haskell.
В некотором роде, это способ отделить виртуальные функции от экземпляров.
struct Foo;
struct IFooMethods;
typedef pair<Foo*,IFooMethods const*> FFF;
struct IFooMethods
{
virtual FFF create() const = 0;
virtual FFF clone(FFF this_) const = 0;
virtual void unary(FFF this_) const = 0;
virtual void binary(FFF this_, FFF other_) const = 0;
};
struct Foo
{
void unary();
void binary(FFF other) { other.second_->unary(other); } // виртуальный вызов other->unary()
};
struct FooMethods : IFooMethods
{
static FooMethods methods;
FFF create() const { return FFF(new Foo(), &methods); }
FFF clone(FFF this_) const { return FFF(new Foo(*this_.first), this); }
void unary(FFF this_) const { this_.first->unary(); }
void binary(FFF this_, FFF other_) const { return this_.first->binary(other_); }
};
struct Bar : Foo { ..... };
struct BarMethods : FooMethods
{
static BarMethods methods;
FFF create() const { return FFF(new Bar(), &methods); }
FFF clone(FFF this_) const { return FFF(new Bar(*this_.first), &methods); }
};
FFF foo, bar, xz;
foo = FooMethods::create();
bar = BarMethods::create();
xz = rand() ? foo.second->clone(foo)
: rand() ? bar.second->clone(bar) // clone - штатный для ООП приём
: bar.second->create(); // а вот create по мотивам - уже не совсем...
foo.second->unary(foo);
foo.second->binary(foo, bar);
Лисп и хаскелл делают это прозрачно, а в С++ приходится громоздить страшное. Так же, как в Си приходится громоздить страшное для реализации обычных виртуальных функций (например, COM-объектов).
Ну и, разумеется, в хаскелле тайпклассы участвуют в системе типов.