Наследование? От лямбды? Ч1
#опытным
Наследованием в языке С++ никого не удивить. Все постоянно его видят в коде и используют. Но что, если я вам скажу, что вы можете наследоваться от лямбды! Как? Давайте разбираться.
Как это вообще возможно?
Ну это не удивительно для тех, кто знает, чем на самом деле являются лямбды. Это по сути своей классы, чей тип знает только сам компилятор, с перегруженным оператором(). То, что ее называют безымянной функцией, это не совсем правда. Все у нее есть.
То есть это класс, у которого есть вполне конкретный метод и даже поля(тут зависит от того, что захватила лямбда).
Значит это вполне легальный кандидат на наследование!
Придется конечно немного поколдовать вокруг отсутствия имени, но для С++ это не проблема.
Ничего особенного. Мы просто создали класс-обертку над каким-то функциональным объектом и используем его оператор(), как свой.
Дальше создаем лямбду и создаем объект обертки, просто передавая лямбду в конструктор. Мы специально не указываем явно шаблонный параметр DerivedFromLambda, потому что мы не знаем настоящего имени лямбды. Мы даем возможность компилятору самому вывести нужный шаблонный тип на основании инициализатора. Это возможно благодаря фиче С++17 Class Template Argument Deduction.
Но даже и на С++11-14 можно написать подобное. Ведь у нас есть оператор decltype, который возвращает в точности тип того выражения, которое мы в него передали. Тогда мы бы создавали объект так:
Зачем это нужно только? К этому мы будем потихоньку подбираться следующие пару постов.
Do surprising things. Stay cool.
#template #cppcore #cpp11 #cpp17
#опытным
Наследованием в языке С++ никого не удивить. Все постоянно его видят в коде и используют. Но что, если я вам скажу, что вы можете наследоваться от лямбды! Как? Давайте разбираться.
Как это вообще возможно?
Ну это не удивительно для тех, кто знает, чем на самом деле являются лямбды. Это по сути своей классы, чей тип знает только сам компилятор, с перегруженным оператором(). То, что ее называют безымянной функцией, это не совсем правда. Все у нее есть.
То есть это класс, у которого есть вполне конкретный метод и даже поля(тут зависит от того, что захватила лямбда).
Значит это вполне легальный кандидат на наследование!
Придется конечно немного поколдовать вокруг отсутствия имени, но для С++ это не проблема.
template<class Lambda>
struct DerivedFromLambda : public Lambda
{
DerivedFromLambda(Lambda lambda) : Lambda(std::move(lambda)) {}
using Lambda::operator();
};
int main(){
auto lambda = []{return 42;};
DerivedFromLambda child{lambda};
std::cout << child() << std::endl;
}
// OUTPUT:
// 42
Ничего особенного. Мы просто создали класс-обертку над каким-то функциональным объектом и используем его оператор(), как свой.
Дальше создаем лямбду и создаем объект обертки, просто передавая лямбду в конструктор. Мы специально не указываем явно шаблонный параметр DerivedFromLambda, потому что мы не знаем настоящего имени лямбды. Мы даем возможность компилятору самому вывести нужный шаблонный тип на основании инициализатора. Это возможно благодаря фиче С++17 Class Template Argument Deduction.
Но даже и на С++11-14 можно написать подобное. Ведь у нас есть оператор decltype, который возвращает в точности тип того выражения, которое мы в него передали. Тогда мы бы создавали объект так:
auto lambda = []{return 42;};
DerivedFromLambda<decltype(lambda)> child{lambda};
Зачем это нужно только? К этому мы будем потихоньку подбираться следующие пару постов.
Do surprising things. Stay cool.
#template #cppcore #cpp11 #cpp17
👍30❤20🔥12🤯10💯2
ref-qualified методы
#опытным
В С++ можно довольно интересными способами перегружать методы класса. Один из самых малоизвестных и малоиспользуемых - помечать методы квалификатором ссылочности.
Чтобы было понятнее. Примерно все знают, что бывают константные и неконстантные методы.
Константные объекты могут вызывать только константные методы. Поэтому мы можем перегрузить метод класса, чтобы он мог работать с константными объектами.
В примере видно что у константного объекта вызывается константная перегрузка.
По аналогии с cv-квалификаторами методов начиная с С++11 существуют ref-квалификаторы. Мы можем перегрузить метод так, чтобы он мог раздельно обрабатывать левые и правые ссылки.
Обратим внимание на сигнатуру методов. Метки ссылочных квалификаторов ожидаемо принимают форму одного и двух амперсандов, по аналогии с типами данных левых и правых сслылок соотвественно. Располагаются они после скобок с аргументами метода.
Работают они примерно также, как вы и ожидаете. lvalue-ref перегрузка вызывается на именованном объекте, rvalue-ref перегрузка - на временном.
Зачем это придумано?
Здесь на самом деле большие параллели с cv-квалификацией методов. Допустим, у вас класс - это какая-то коллекция. И вы хотите давать пользователям доступ к элементам этой коллекции через оператор[]. Для неконстантных объектов удобно возвращать ссылку. А вот для константных возвращение ссылки - потенциальное нарушение неизменяемости объекта. Поэтому в таких случаях константный оператор может возвращать элемент по значению или по константной ссылке.
Также и с ссылочностью. В каких-то случаях оптимально или просто необходимо использовать для правых ссылок иную логику метода.
Подробнее об этом чуде-юде будем разбираться в следующих постах.
Stay flexible. Stay cool.
#cpp11 #design
#опытным
В С++ можно довольно интересными способами перегружать методы класса. Один из самых малоизвестных и малоиспользуемых - помечать методы квалификатором ссылочности.
Чтобы было понятнее. Примерно все знают, что бывают константные и неконстантные методы.
struct SomeClass {
void foo() {std::cout << "Non-const member function" << std::endl;}
void foo() const {std::cout << "Const member function" << std::endl;}
};
SomeClass nonconst_obj;
const SomeClass const_obj;
nonconst_obj.foo();
const_obj.foo();
// OUTPUT
// Non-const member function
// Const member function
Константные объекты могут вызывать только константные методы. Поэтому мы можем перегрузить метод класса, чтобы он мог работать с константными объектами.
В примере видно что у константного объекта вызывается константная перегрузка.
По аналогии с cv-квалификаторами методов начиная с С++11 существуют ref-квалификаторы. Мы можем перегрузить метод так, чтобы он мог раздельно обрабатывать левые и правые ссылки.
struct SomeClass {
void foo() & {std::cout << "Call on lvalue reference" << std::endl;}
void foo() && {std::cout << "Call on rvalue reference" << std::endl;}
};
SomeClass lvalue;
lvalue.foo();
SomeClass{}.foo();
// OUTPUT
// Call on lvalue reference
// Call on rvalue reference
Обратим внимание на сигнатуру методов. Метки ссылочных квалификаторов ожидаемо принимают форму одного и двух амперсандов, по аналогии с типами данных левых и правых сслылок соотвественно. Располагаются они после скобок с аргументами метода.
Работают они примерно также, как вы и ожидаете. lvalue-ref перегрузка вызывается на именованном объекте, rvalue-ref перегрузка - на временном.
Зачем это придумано?
Здесь на самом деле большие параллели с cv-квалификацией методов. Допустим, у вас класс - это какая-то коллекция. И вы хотите давать пользователям доступ к элементам этой коллекции через оператор[]. Для неконстантных объектов удобно возвращать ссылку. А вот для константных возвращение ссылки - потенциальное нарушение неизменяемости объекта. Поэтому в таких случаях константный оператор может возвращать элемент по значению или по константной ссылке.
Также и с ссылочностью. В каких-то случаях оптимально или просто необходимо использовать для правых ссылок иную логику метода.
Подробнее об этом чуде-юде будем разбираться в следующих постах.
Stay flexible. Stay cool.
#cpp11 #design
🔥29👍12❤4🤯3