LINUX.ORG.RU

Автовывод типов методов в С++

 ,


0

2

Смотрел про это на Википедии, там сказано, что запись auto function() или decltype(auto) function () работает, если компилятор может вывести из тела функции возвращаемый ей тип. А как такая штука работает с методами класса?

Метод класса obj.f(...) — это просто перегруженная по типу первого аргумента функция f(obj, ...)

Crocodoom ★★★★★ ()

Сейчас под рукой только MSVC - нормально работает.

rumgot ★★★★★ ()
Ответ на: комментарий от panter_dsd

А чем метод отличается от функции? О_о

Это магия фокуса - тссссс.

rumgot ★★★★★ ()

Если более конкретно, то предположим я делаю так:

 class Foo
{
     static auto member (int);
}

auto Foo::member (int param) 
{
   if(param < 5) return 3;
   else return 5;
}

В main пишу так:

   auto item = Foo::member (4);
На что получаю ошибку: function 'member' with deduced return type cannot be used before it is defined.

LongLiveUbuntu ★★★★★ ()
Ответ на: комментарий от LongLiveUbuntu

Статический метод можно считать обычным методом, который ничего не делает со своим первым аргументом obj

Ничего не делает, но по-прежнему его принимает — чтобы обеспечить перегрузку по типу.

Crocodoom ★★★★★ ()
Ответ на: комментарий от LongLiveUbuntu

Подозреваю что перенести тело метода в тело класса, он не может вывести тип так как не знает реализации.

dyb4hzvo ()
Ответ на: комментарий от Crocodoom

Static member functions are not associated with any object. When called, they have no this pointer. The address of a static member function may be stored in a regular pointer to function.

LZai ()
Ответ на: комментарий от Crocodoom

Статический метод можно считать обычным методом, который ничего не делает со своим первым аргументом obj

@LZai права. Статические функции класса не принимают никакой pointer первым аргументом.

fsb4000 ★★★★★ ()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от fsb4000

Речь не о поинтерах, а о выводе типов. С точки зрения ФП любой метод — это функция с перегрузкой (диспетчеризацией) по первому аргументу. Точнее, по его типу. Поэтому никаких принципиальных проблем с выводом типов тут быть не может. А что нам навертели с auto в очередном компиляторе C++ — уже частности

Crocodoom ★★★★★ ()
Ответ на: комментарий от LZai

По типу компилятор выбирает, какой именно указатель на статический метод использовать. Если одноименных методов несколько - у разных классов.

Crocodoom ★★★★★ ()
Ответ на: комментарий от LongLiveUbuntu

Тут нужно понимать, что при компиляции main.cpp компилятор не заглядывает в .cpp файл в котором находится определение твоей auto-функции. Компилятор смотрит только в заголовочный файл. Ты можешь поместить определение функции за пределами класса, но в заголовочном файле, но тогда в пределах одной программы нельзя будет включить этот заголовочный файл куда-то еще. Думаю в твоем случае лучше использовать шаблон функции, там тоже можно использовать возвращаемый тип как auto и при этом можно будет включать заголовочный файл в разные места. И шаблонная функция может быть не inline (тут уж как компилятор решит). Ну т.е. сделай так:

#ifndef TEST_H
#define TEST_H

template <typename T = int> auto test() { return 5; }

#endif // TEST_H

И потом используй где хочешь:

#include "test.h"

int main() {
  test();
}
rumgot ★★★★★ ()
Последнее исправление: rumgot (всего исправлений: 6)

То есть по сути этот автовывод типов для раздельной компиляции файлов не работает. Очень хорошо, так и запишем.

LongLiveUbuntu ★★★★★ ()
Ответ на: комментарий от LongLiveUbuntu

Да. Получается так. Ну т.е. он не работает для раздельной компиляции файлов при использовании с НЕ-шаблонными функциями. Use templates, Luke.

rumgot ★★★★★ ()
Последнее исправление: rumgot (всего исправлений: 2)
Ответ на: комментарий от Crocodoom

С точки зрения ФП любой метод — это функция с перегрузкой (диспетчеризацией) по первому аргументу. Точнее, по его типу.

В рамках этого определения статический метод не является методом.

monk ★★★★★ ()
Ответ на: комментарий от monk
struct A {
  static void print_class() {
    printf("A\n");
  }
};

struct B {
  static void print_class() {
    printf("B\n");
  }
};

int main()
{
    A().print_class();
    B().print_class();

    A::print_class();
    B::print_class();

    return 0;
}

Автовывод типов методов в С++ (комментарий)

Просто механизм неймспейсов эту диспечтеризацию заметает под ковёр. Но от указания типа (классовой принадлежности) *this при вызове print_class тебе никуда не деться. А это и есть диспетчеризация by definition.

Crocodoom ★★★★★ ()
Ответ на: комментарий от Crocodoom

классовой принадлежности

⭐☭

anonymous ()
Ответ на: комментарий от Crocodoom

Но от указания типа (классовой принадлежности) *this при вызове print_class тебе никуда не деться.

Технически это не тип, а пространство имён. Также как std::ios::binary, например. Или будешь утверждать, что у этого числа есть класс std::ios?

monk ★★★★★ ()
Ответ на: комментарий от monk

Технически это не тип, а пространство имён

Пространство имён, которое однозначно связано с классом. Это всё ещё диспетчеризация. А то что она реализована через неймспейсы — ну, у C++ много «особенностей»...

У нас тут вышел терминологический спор, поэтому без определений мы далеко не уедем

  1. Статический метод класса — это такой метод, к которому можно обратиться не создавая объект данного класса.

  2. В более традиционных объектно-ориентированных языках программирования с одиночной диспетчеризацией при вызове метода (отправке сообщения в Smalltalk, вызове функции-члена в C++), один из его аргументов рассматривается особым образом и используется для определения того, какой из (потенциально многих) методов с этим именем должен быть вызван. Во многих языках этот особый аргумент обозначается синтаксически, например, в ряде языков программирования специальный аргумент помещается перед точкой при вызове метода

Вроде всё понятно. Теперь посмотрим, как статические методы сделаны в других языках, чтобы отделить мух от котлет. То бишь ООП в C++ от ООП вообще. Например в Питоне:

class A:
    def foo(self):
        print("Foo")
    @staticmethod
    def bar():
        print("Bar")

A().foo()
A.bar()

«Точечка» присутствует и вызове обычного метода, и в вызове статического. Напомню, что точечка в питоне означает поиск метода в словаре соответсвующего объекта. В каком именно словаре искать — стоит слева от точечки, и именно это и есть диспетчеризация по-питоньи.

Возвращаемся к методам в C++. Для обычных тоже используется ., для статических ::. Отличие в том, что точка может работать динамически (с virtual), а вот :: парсится на этапе компиляции и «хрен-знает-какой» класс туда уже не подсунуть. Тем не менее, согласно определениям выше это по-прежнему диспетчеризация. Просто диспетчеризацию статических методов производит компилятор С++, а не рантайм.

Статические методы могут выбираться и в рантайме. Это будет работать в любом языке, где классы являются first-class citizen. Например, в Питоне. Но не в C++. Просто в C++ от этой «лишней» возможности отказались в угоду оптимизации. Но это совершенно не повод вводить какую-то свою, птичью терминологию.

Crocodoom ★★★★★ ()
Ответ на: комментарий от LongLiveUbuntu

Статический метод ничем не отличается от обычной функции, только лежит в пространстве имён объекта и имеет доступ к его private и protected полям и методам. На уровне ассемблера разницы между обычной функцией и статическим методом нет никакой (в отличии от обычного метода, которому под капотом добавляют дополнительный аргумент this).

KivApple ★★★★★ ()
Ответ на: комментарий от Crocodoom

Вообще-то, у статического метода вовсе нет аргумента obj, потому что статический метод может быть вызван без экземпляра класса. По сути дела статический метод это просто сахар, чтобы запихнуть функцию в пространство имён класса и дать ей доступ к его приватным полям и методам.

KivApple ★★★★★ ()
Ответ на: комментарий от Crocodoom

Например в Питоне

В питоне «статические методы» являются методами класса. В смысле, сам класс является объектом. К слову, у этого объекта свой класс есть: метакласс называется.

Статические методы могут выбираться и в рантайме. Это будет работать в любом языке, где классы являются first-class citizen.

Покажи в CLOS.

Для обычных тоже используется ., для статических ::.

Именно это и показывает, что для статических методов класс используется только как пространство имён. Также как для типов и для констант.

monk ★★★★★ ()
Ответ на: комментарий от Crocodoom

Но это совершенно не повод вводить какую-то свою, птичью терминологию.

Вот очень спорно, чью терминологию считать птичьей. Сейчас про ООП существует как минимум 3 семантики:

  • Smalltalk: метод это сообщение. Любому объекту можно отправить любое сообщение.

  • CLOS: метод это функция. Тело функции выбирается по классам аргументов. Пространства имён определяются пакетами, к классам отношения не имеют.

  • C++: класс определяет пространство имён. Методы класса это особые функции, определённые в пространстве имён класса. Если им передаётся объект класса через параметр this, то обычные методы, иначе статические.

Питон взял в целом сеиантику C++, но переопределил понятие «статический» в «метод объекта класса».

monk ★★★★★ ()
Ответ на: комментарий от monk

В питоне «статические методы» являются методами класса. В смысле, сам класс является объектом. К слову, у этого объекта свой класс есть: метакласс называется.

Спасибо, я про метаклассы в курсе. Между прочим, статические методы есть и в классе, и в инстансе класса. Проверяется через dir().

Покажи в CLOS

CLOS не владею

Именно это и показывает, что для статических методов класс используется только как пространство имён.

Ну и что? Тогда диспетчеризация = выбор конкретного пространства имён. Что это меняет?

Crocodoom ★★★★★ ()
Ответ на: комментарий от monk

C++: класс определяет пространство имён. Методы класса это особые функции, определённые в пространстве имён класса. Если им передаётся объект класса через параметр this, то обычные методы, иначе статические.

Ты путаешь идейные, теоретические вещи и чистую прикладуху. В C++ так сделано, чтобы обеспечить зеро-кост и вот это вот всё. Кому нужно нормальное (читай мощное) ООП, берут языки высокого уровня. Где можно редактировать это самое «пространство имён» в рантайме.

Crocodoom ★★★★★ ()
Ответ на: комментарий от Crocodoom

Где можно редактировать это самое «пространство имён» в рантайме.

Ну вот берём CLOS. Всё можно делать в рантайме.

Есть пространства имён: foo:run, bar:run. Есть диспетчеризация по аргументам (foo:run 1) и (foo:run «ok») вызовут разные тела функций. Более того, метод и класс могут быть из разных пространств имён: (foo:run (make-instance ’bar:my-class)) и даже можно сделать диспетчеризацию по значениям и по второму-третьему аргументу функции. (baz 1 2) (baz 1 «ok») и (baz 1 0) могут иметь каждый своё тело функции (по классу второго аргумента: целое, строка и «равно 0»).

И если мне вдруг нужен «статический метод класса» в CLOS, это будет обычная функция в соответствующем пространстве имён.

Кому нужно нормальное (читай мощное) ООП, берут языки высокого уровня. Где можно редактировать это самое «пространство имён» в рантайме.

Очень мало задач требуют возможность менять типы данных без остановки выполнения программы.

monk ★★★★★ ()
Ответ на: комментарий от monk

Очень мало задач требуют возможность менять типы данных без остановки выполнения программы.

Именно менять — не знаю, но создавать новые? Это из той же оперы. В питоне я могу создать класс в функции, заполнить его методами (в том числе статическими) и вернуть класс в вызывающий контекст. А потом вызвать статический метод в этом контексте. Это диспетчеризация в чистом виде, поскольку заранее про класс ничего неизвестно.

Очень мало задач

И вообще, это субъективное и некорректное утверждение. Обосновать ты его не сможешь, поскольку оно недоказуемо и неопровергаемо. Так-то можно вообще на голых сях любую задачу решить.

Любой, кто владеет метаклассами, применит вышеописанный трюк там, где это будет уместно. Лично я применял, чесслово. Другое дело, что большинство питонистов считают метаклассы «чёрной магией», и используют питон как «дёргалку библиотек».

Crocodoom ★★★★★ ()
Последнее исправление: Crocodoom (всего исправлений: 1)
Ответ на: комментарий от monk

Есть пространства имён: foo:run, bar:run. Есть диспетчеризация по аргументам (foo:run 1) и (foo:run «ok») вызовут разные тела функций. Более того, метод и класс могут быть из разных пространств имён: (foo:run (make-instance ’bar:my-class)) и даже можно сделать диспетчеризацию по значениям и по второму-третьему аргументу функции. (baz 1 2) (baz 1 «ok») и (baz 1 0) могут иметь каждый своё тело функции (по классу второго аргумента: целое, строка и «равно 0»).

Что здесь вообще есть класс? Что есть метод класса, инстанс класса? Пока непонятно.

Crocodoom ★★★★★ ()
Ответ на: комментарий от Crocodoom
;; вот класс
(in-package :bar)
(defclass my-class (superclass) (field1 field2))

;; вот метод
(in-package :foo)
(defgeneric run (arg))
(defmethod run ((arg bar:my-class)) (print "Run foo(bar:my-class)"))

(defmethod run ((arg string)) (print "Run foo(string)"))

(defgeneric baz (arg1 arg2))
(defmethod baz ((arg1 t) (arg2 (eql 0))) (print "Run baz(_ 0)"))
(defmethod baz ((arg1 integer) (arg2 string)) (print "Run baz(integer string)"))

;; вот использование
(in-package main)
(foo:run (make-instance ’bar:my-class))
; (make-instance ’bar:my-class) = создать инстанс класса bar:my-class. Почти дословно по-английски написано.
monk ★★★★★ ()
Ответ на: комментарий от monk

И если мне вдруг нужен «статический метод класса» в CLOS, это будет обычная функция в соответствующем пространстве имён.

Эээ..?! Значит, в CLOS статических методов просто нет. Только (обычные) методы и (обычные) функции.

Я опять-таки напомню определение

Статический метод класса — это такой метод, к которому можно обратиться не создавая объект данного класса.

В C++ или Python к любому методу можно обратиться как A().method. К статическому тоже. Именно это делает его методом. А статическим его делает то, что к нему можно обратиться также как A::method или A.method.

CLOS: метод это функция. Тело функции выбирается по классам аргументов. Пространства имён определяются пакетами, к классам отношения не имеют.

В рамках этой парадигмы статических методов не существует

Crocodoom ★★★★★ ()
Последнее исправление: Crocodoom (всего исправлений: 1)
Ответ на: комментарий от monk

А ещё в питоне* можно делать статические (чисто) виртуальные методы. То есть статические методы, которые можно (необходимо) переопределять в дочерних классах. Полезная штука для грамотного проектирования интерфейсов.

*Если верить SO, это не уникальная фича питона. Так же можно, например, в Delphi.

Crocodoom ★★★★★ ()
Ответ на: комментарий от Crocodoom

В рамках этой парадигмы статических методов не существует

Так я про это и намекаю. Статические методы существуют только в семантике C++. В Smalltalk метод = сообщение объекту. И что-то сообщить не созданному объекту невозможно. В CLOS метод = обобщённая функция по классам аргументов.

В C++ статические методы потребовались исключительно из-за того, что на классы повесили функции пространств имён. И в этом смысле считать, что терминология Python, унаследованная от C++ и модифицированная, является правильной, а изначальная терминология C++ — птичья, выглядит предвзято.

monk ★★★★★ ()
Ответ на: комментарий от monk

В C++ статические методы потребовались исключительно из-за того, что на классы повесили функции пространств имён

статический метод класса появляется автоматом, если у вас есть общее свойство(инвариант) данного класса, существующие даже если нет ни одного экземпляра класса. например константа, (ну не будете же вы число пи привязывать к экземпляру класса Math?), размер экземпляра класса(он постоянный), имя класса, или еще какое-то общее свойство в виде функции возвращающей нечто. если этого не делать, вы не сможете нормально описывать инвариантные свойства класса.

alysnix ★★ ()
Ответ на: комментарий от alysnix

ну не будете же вы число пи привязывать к экземпляру класса Math?

А с чего это Math является классов? То есть, чем отличаются объекты этого класса? Как определяется их внутреннее состояние?

Константа или общее свойство вообще не должны быть внутри класса.

Это может быть модуль, пакет, пространство имён, но никак не класс объектов.

monk ★★★★★ ()
Ответ на: комментарий от monk

А с чего это Math является классов? То есть, чем отличаются объекты этого класса? Как определяется их внутреннее состояние?

не цепляйтесь к деталям.

Константа или общее свойство вообще не должны быть внутри класса.

должны быть. например тег всех обьектов класса при сериализации, имя класса, выводимое пользователю, константы внутренне присущие данному классу и более никому, и так далее. да просто счетчик обьектов данного класса созданных при работе программы.

class A{
  static int _count;
  ...
  A(){ ++_count; }
  ~A(){ --_count; }

  static int getCount(){return _count;}
}
alysnix ★★ ()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

Тег и имя — это статические поля. Или в других языках поля класса. К слову, в CLOS на них не накладывается требование «можно обратиться не создавая объект данного класса».

А счётчик экземпляров на CLOS, как правило, имеет API: getCount(class). И к какому-то конкретному классу из-за этого принадлежать не может.

К слову, счётчик в примере будет считать не объекты именно этого класса, а включая всех потомков.

monk ★★★★★ ()
Ответ на: комментарий от monk

Тег и имя — это статические поля. Или в других языках поля класса. К слову, в CLOS на них не накладывается требование «можно обратиться не создавая объект данного класса».

CLOS тут не интересует вообще. я говорил в общем смысле. у класса в ООП ДОЛЖНЫ быть некое общее состояние,и инварианты, константы и все такое, привязанные именно к классу, а не его экземпляру. и как это реализовано в разных языках - неважно.

К слову, счётчик в примере будет считать не объекты именно этого класса, а включая всех потомков.

он будет считать все экземпляры, совместимые с данным классом. и это то, что нам нужно.

так что в плюсах все пучком по этому поводу

alysnix ★★ ()
Ответ на: комментарий от monk

А счётчик экземпляров на CLOS, как правило, имеет API: getCount(class). И к какому-то конкретному классу из-за этого принадлежать не может.

это вообще не аргумент. я могу хотеть считать например - максимальное количество экземпляров обьекта созданных в программе, число обектов, созданное в определенном интервале времени, среднее число обьектов, и вообще все, что понадобится.

есть в вашем clos, все эти возможности «не привязанные к классу»?

alysnix ★★ ()
Ответ на: комментарий от alysnix

есть в вашем clos, все эти возможности «не привязанные к классу»?

Ага. Они привязаны к пакету. Даже в C++ так можно. В чём принципиальная разница между

class A{
  static int _count;
  ...
  A(){ ++_count; }
  ~A(){ --_count; }

  static int getCount(){return _count;}
}

и

namespace A {
  int _count;
  extern int getCount(){return _count;}
  class A{
    ...
    A(){ ++_count; }
    ~A(){ --_count; }

  }
}

?

monk ★★★★★ ()
Ответ на: комментарий от monk

Ага. Они привязаны к пакету. Даже в C++ так можно. В чём принципиальная разница между

разница большая.

  1. лишняя сущность - неймспейс.
  2. счетчик открыт извне и может быть модифицирован. в классе же он приватный.
  3. доступ к клаcсу во втором случае - A::A
  4. неймспейс нужен только для инкапсуляции статической переменной, и статической функции и привязана она не к классу, а находится в неймспейсе. в менее тривиальном случае вообще непонятно назначение этой переменной.

опять же статические функции хороши для реализации синглетонов или фабрик, когда конструктор и деструктор приватные, то есть класс нельзя создать никак кроме вызова публичной функции взятия экземпляра, типа

class A{
private:
  A();
  ~A();
public:
  static A* get_instance()
  static void utilize(A*)
}

тут все закрыто, а создание и уничтожение обьекта можно сделать только через специальные функции.

alysnix ★★ ()
Ответ на: комментарий от alysnix

лишняя сущность - неймспейс.

А с моей точки зрения лишняя функциональность в классе: эмуляция неймспейса.

Например если надо счётчик для нескольких классов сделать, будешь им общего предка добавлять? Или если действительно надо считать именно объекты текущего класса. Через функцию в неймспейсе делается тривиально.

счетчик открыт извне и может быть модифицирован. в классе же он приватный.

С чего это? В неймспейсе он вообще только в .cpp присутствует. А вот в классе придётся эту особенность реализации в заголовок вытаскивать.

доступ к клаcсу во втором случае - A::A

Если нет конфликтов имён, первая команда using namespace. Вообще-то классы по неймспейсам рекомендуется распихивать чтобы случайно с именем из другого файла не пересечься.

тут все закрыто, а создание и уничтожение обьекта можно сделать только через специальные функции.

И позволяет писать в стиле

A* a = A::get_instance();
A* b = a.get_instance();
a.utilize(b);

Логичнее для фабрики сделать дружественную функцию вместо статической.

Статические методы имеют смысл, если они всё-таки используются как методы. Типа

a = new Dog();
cout << a.legs(); // всегда 4

Если вдруг потом надо будет учитывать хромых собак, можно будет заменить статический метод на обычный.

monk ★★★★★ ()
Последнее исправление: monk (всего исправлений: 1)
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.