Существует ли реальный статический полиморфизм в 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++, и я не должен смотреть на как компилятор производит вывод.


person Community    schedule 26.12.2013    source источник
comment
@JoeZ, а почему это не статический полиморфизм?   -  person jalf    schedule 26.12.2013
comment
@jalf: у меня в голове была неправильная идея. Я удалю свой комментарий.   -  person Joe Z    schedule 26.12.2013


Ответы (5)


Существует ли реальный статический полиморфизм в 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.

Но то, чего мы пытаемся достичь с помощью полиморфизма, носит более общий характер:

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

    • that is, without the program source code containing if (type == X) f1(x) else f2(x);-style code
  • уменьшение затрат на обслуживание, так как после явного изменения типа переменной требуется меньше последовательных изменений, которые необходимо вносить вручную в исходный код.

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

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

    • actually known as "instantiation" for templates and "substitution" for preprocessor macros, but I'll use "instantiation" hereafter for convenience; conceptually, re-compilation or re-interpretation...
  2. неявная отправка (статическая или динамическая) в определенное поведение (машинный код), соответствующее различные типы обрабатываемых данных.

... и в некоторых второстепенных отношениях согласно моему ответу на Полиморфизм в c++

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

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

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

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

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

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 и константы-указатели, и использовать их для самых разных целей без изменения типов данных и, следовательно, без участия полиморфизма. Макросы еще более гибкие.


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


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

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

person Community    schedule 26.12.2013

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

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

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

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

Первый относится к перегрузке функций. Третий тип относится к позднему связыванию или полиморфизму времени выполнения, который вы могли бы увидеть, например, в наследовании. Нас интересует второй.

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

Например:

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), находя и выполняя код, соответствующий типу.

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

person Community    schedule 26.12.2013

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

Примеры статического полиморфизма включают в себя простую перегрузку функций, шаблоны функций, которые могут работать с выводом типа, признаками типов и < em>любопытно повторяющийся шаблон шаблона (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
}
person juanchopanza    schedule 26.12.2013
comment
и, говоря о будущем C++, есть небольшая надежда на какой-то полиморфизм типа компиляции с использованием concepts, который, вероятно, станет важной частью следующего стандарта C++14. - person user2485710; 26.12.2013
comment
я ничего не минусовал. У меня есть предложение, может быть, вы могли бы упомянуть черты типа в своем ответе, решение для бедных для C ++ без concepts, но я не думаю, что в вашем сообщении есть что-то неправильное. - person user2485710; 26.12.2013
comment
@user2485710 user2485710 Я добавил признаки типа в список примеров. Это не был исчерпывающий список, но это хороший пример. - person juanchopanza; 26.12.2013
comment
Здесь нет признаков типа... Это не то же самое, что rtti. - person rubenvb; 26.12.2013
comment
@rubenvb никто не говорит, что здесь есть черты типа. - person juanchopanza; 26.12.2013
comment
О, я неправильно прочитал и предположил, что вы имели в виду пример кода. Не берите в голову :-) - person rubenvb; 26.12.2013

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

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

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

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

person πάντα ῥεῖ    schedule 26.12.2013

Хотя можно утверждать, что пример в 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, возможно, растянут, концепция определенно существует. Чего не хватает, так это не возможности переопределять функции, а возможности для специализированных классов автоматически наследовать функции, поведение которых не меняется.

person Glenn Teitelbaum    schedule 26.12.2013