Есть ли настоящий статический полиморфизм в C++?

Вот простой код на C++:

#include <iostream>
#include <typeinfo>

template<typename T>
void function()
{
   std::cout << typeid(T).name() << std::endl;
}

int main()
{
   function<int>();
   function<double>();
   return 0;
}

Я читал, что шаблоны в C++ - это функция времени компиляции, которая не похожа на дженерики в C#/Java.

Итак, как я понял, компилятор C++ разделит одну определенную функцию на различное количество (зависит от количества вызовов с различным типом) функций.

Я прав или нет? Я не специалист по компиляторам C++, поэтому прошу у вас совета.

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

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

5 ответов

Решение

Есть ли настоящий статический полиморфизм в C++?

Безусловно - существует три механизма статического полиморфизма: шаблоны, макросы и перегрузка функций.

Итак, как я понял, компилятор C++ разделит одну определенную функцию на различное количество (зависит от количества вызовов с различным типом) функций. Я прав или нет?

Это общая идея. Количество функций, которые создаются, зависит от количества перестановок параметров шаблона, которые могут быть явно указаны, как в function<int> а также function<double> или - для шаблонов, которые используют параметры шаблона для сопоставления с аргументами функции - автоматически выводятся из аргументов функции, например:

template <typename T, size_t N>
void f(T (&array)[N])
{ }

double d[2];
f(d);   // instantiates/uses f<double, 2>()

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


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

На самом деле, нет.

  • template<> function создается для двух типов

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

    • тривиально, во время таких реализаций typeid(T) оценивается для int а также double и эффективно ведет себя полиморфно с точки зрения программиста (хотя это ключевое слово компилятора - реализация неизвестна)

  • тривиально, сочетание статического и номинально динамического (но здесь, вероятно, оптимизируемого до статического) полиморфизма поддерживает ваше использование std::cout

Фон - полиморфизм и генерация кода

Требование, которое я считаю решающим для полиморфизма:

  • когда код скомпилирован (будь то "нормальный" код или по экземпляру шаблона или подстановке макроса), компилятор автоматически выбирает (создает при необходимости) - и встраивает, или вызывает - отличное поведение, соответствующее типу (машинный код)

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

    • например, std::cout << x; полиморфно вызывает другой код как тип x варьируется, но все еще выводит x значение, в то время как неполиморфный printf("%d", x) ручки int с, но должен быть вручную изменен на printf("%c", x); если x становится char,

Но то, что мы пытаемся достичь с помощью полиморфизма, является более общим:

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

    • то есть без исходного кода программы, содержащего if (type == X) f1(x) else f2(x); код стиля
  • уменьшенная нагрузка на обслуживание, так как после явного изменения типа переменной требуется меньше последовательных изменений во всем исходном коде

Эти большие аспекты изображения поддерживаются в C++ следующим образом:

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

    • фактически известный как "создание экземпляров" для шаблонов и "замена" макросов препроцессора, но для удобства я буду использовать "создание экземпляров" в дальнейшем; концептуально, перекомпиляция или реинтерпретация...
  2. неявная отправка (статическая или динамическая) в отличное поведение (машинный код), соответствующее определенному типу (типам) обрабатываемых данных.

... и в некоторых незначительных аспектах, согласно моему ответу на Полиморфизм в C++

Различные типы полиморфизма включают один или оба из них:

  • dispatch (2) может происходить во время создания экземпляра (1) для шаблонов и макросов препроцессора,

  • instantiation (1) обычно происходит во время dispatch (2) для шаблонов (без совпадения с полной специализацией) и функционально-подобных макросов (вроде циклических, хотя макросы не раскрываются рекурсивно)

  • dispatch (2) может происходить без создания экземпляра (1), когда компилятор выбирает существующую перегрузку функции или специализацию шаблона или когда компилятор запускает виртуальную / динамическую диспетчеризацию.

Что на самом деле использует ваш код?

function<int> а также function<double> повторно использовать function шаблон кода для создания отдельного кода для каждого из этих типов, поэтому вы получаете экземпляр (1), как указано выше. Но вы жестко программируете, какую инстанцию ​​вызывать, вместо того, чтобы компилятор неявно выбирал инстанциацию на основе типа некоторого параметра, т. Е. Поэтому вы не используете напрямую неявную диспетчеризацию ala (2) при вызове function, В самом деле, function отсутствует параметр, который компилятор мог бы использовать для неявного выбора экземпляра шаблона.

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

Так что же было бы однозначно полиморфным?

Для того, чтобы проиллюстрировать, как шаблоны могут поддерживать отправку (2), а также создание экземпляра (1) и бесспорный обеспечивают "полиморфизм", рассмотреть следующие вопросы:

template<typename T>
void function(T t)
{
    std::cout << typeid(T).name() << std::endl;
}

function(4);      // note: int argument, use function<int>(...)
function(12.3);   // note: double argument, use function<double>(...)

Приведенный выше код также использует неявную отправку в соответствующий типу код - аспект "2". выше - полиморфизм.


Не типовые параметры

Интересно, что C++ предоставляет возможность создавать шаблоны с интегральными параметрами, такими как логические, int и константы указателя, и использовать их для любых действий без изменения типов данных и, следовательно, без какого-либо участия в полиморфизме. Макросы еще более гибкие.


Обратите внимание, что использование шаблона в стиле CRTP НЕ является обязательным требованием для статического полиморфизма - это пример его применения. Во время реализации компилятор демонстрирует статический полиморфизм при сопоставлении операций с реализациями в типе, заданном параметром.


Обсуждение терминологии

Получить окончательное определение полиморфизма сложно. Википедия цитирует онлайн-глоссарий Бьярна Страуструпа, "обеспечивающий единый интерфейс для сущностей разных типов": это подразумевает struct X { void f(); }; struct Y { void f(); }; уже проявляет полиморфизм, но ИМХО мы получаем полиморфизм только тогда, когда мы используем соответствие интерфейса из клиентского кода, например template <typename T> void poly(T& t) { t.f(); } требует статической полиморфной отправки t.f() для каждого экземпляра.

В Википедии перечислены три типа полиморфизма:

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

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

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

Первый относится к перегрузке функций. Третий тип относится к позднему связыванию или полиморфизму во время выполнения, который вы увидите, например, в наследовании. Второе - это то, что нас интересует.

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

Например:

template <typename T, typename U>
auto func(const T& t, const U& u) -> decltype(t + u)
{
   return (t + u);
}

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

Тем не менее, в вашем примере у вас есть экземпляры для ваших функций, которые являются различными, function<int> а также function<double>, Вот цитата:

Чтобы быть полиморфным, [a()] должен иметь возможность работать со значениями как минимум двух различных типов (например, int и double), находя и выполняя код, соответствующий типу.

В этом случае экземпляры являются специфическими для типа, в котором они были созданы, поэтому полиморфизм не задействован.

В вашем примере нет статического полиморфизма, потому что нет полиморфизма. Это потому что function<int>() не выглядит так же, как function<double>(),

Примеры статического полиморфизма могут включать в себя простую перегрузку функций, шаблоны функций, которые могут работать с выводом типа, черты типа и любопытно повторяющийся шаблон (CRTP). Так что этот вариант в вашем примере будет квалифицирован как статический полиморфизм:

#include <iostream>
#include <typeinfo>

template<typename T>
void function(T)
{
   std::cout << typeid(T).name() << std::endl;
}

int main()
{
   function(0);   // T is int
   function(0.0); // T is double
   return 0;
}

Вот еще один пример:

template<typename T>
void function(T t)
{
  t.foo();
}

struct Foo() 
{
  void foo() const {}
};

struct Bar() 
{
  void foo() const {}
};

int main()
{
  Foo f;
  Bar b;
  function(f); // T is Foo
  function(b); // T is Bar
}

Для C++ термин "статический полиморфизм" обычно используется, например, для шаблонов проектирования типа CRTP:

template<typename Derived> 
class Base
{
      void someFunc() {
          static_cast<Derived*>(this)->someOtherFunc();
      };
};

class ADerived : public Base<ADerived>
{
    void someOtherFunc() { 
        // ... 
    }
};

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

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

template<class T>
class Base
{
public:
      int a() { return 7; }
};

template<>
class Base<int>
{
public:
      int a() { return 42; }
};

template<>
class Base<double>
{
public:
      int a() { return 121; }
};

Здесь мы видим, что для большинства классов a() вернет 7; Специализированные (производные) экземпляры для int а также double может иметь радикально различное поведение, демонстрируемое в простом случае различными возвращаемыми значениями, то же самое может быть сделано для шаблонов, например, с параметрами int, и может демонстрировать то, что может быть странным образом названо статическим рекурсивным полиморфизмом.

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

Другие вопросы по тегам