




Пришлось искать CRTP. Однако, сделав это, я нашел кое-что о Статический полиморфизм. Подозреваю, что это ответ на ваш вопрос.
Оказывается, ATL довольно широко использует этот шаблон.
Этот В Википедии есть все, что вам нужно. А именно:
template <class Derived> struct Base
{
void interface()
{
// ...
static_cast<Derived*>(this)->implementation();
// ...
}
static void static_func()
{
// ...
Derived::static_sub_func();
// ...
}
};
struct Derived : Base<Derived>
{
void implementation();
static void static_sub_func();
};
Хотя я не знаю, сколько это на самом деле тебе дает. Накладные расходы на вызов виртуальной функции (конечно, зависят от компилятора):
В то время как накладные расходы статического полиморфизма CRTP составляют:
Кстати, ваш анализ CRTP неверен. Должно быть: Память: Ничего, как сказал Дин Майкл. Время выполнения: один (более быстрый) вызов статической функции, а не виртуальной, и в этом весь смысл упражнения. static_cast ничего не делает, он просто позволяет скомпилировать код.
Я хочу сказать, что базовый код будет дублироваться во всех экземплярах шаблона (то самое слияние, о котором вы говорите). Это похоже на наличие в шаблоне только одного метода, который полагается на параметр шаблона; все остальное лучше в базовом классе, иначе он втягивается («объединяется») несколько раз.
Каждый метод в базе будет снова скомпилирован для каждого производного. В (ожидаемом) случае, когда каждый экземпляр метода отличается (из-за того, что свойства Derived различны), это не обязательно может считаться накладными расходами. Но это может привести к увеличению общего размера кода по сравнению с ситуацией, когда сложный метод в (нормальном) базовом классе вызывает виртуальные методы подклассов. Кроме того, если вы поместите служебные методы в Base <Derived>, которые на самом деле вообще не зависят от <Derived>, они все равно будут созданы. Возможно, глобальная оптимизация несколько это исправит.
Вызов, который проходит через несколько уровней CRTP, будет расширяться в памяти во время компиляции, но может легко сократиться через TCO и встраивание. Тогда CRTP сам по себе не виноват, не так ли?
Я сам искал достойные обсуждения CRTP. Методы научного C++ Тодда Велдхуизена - отличный ресурс для этого (1.3) и многих других продвинутых методов, таких как шаблоны выражений.
Кроме того, я обнаружил, что вы можете прочитать большую часть оригинальной статьи Коплиена о C++ Gems в книгах Google. Может быть, это все еще так.
@fizzer Я прочитал предложенную вами часть, но все еще не понимаю, что означает двойная сумма шаблона <class T_leaftype> (Matrix <T_leaftype> & A); покупает вас по сравнению с шаблоном <class Whatever> с двойной суммой (Whatever & A);
@AntonDaneyko При вызове базового экземпляра вызывается сумма базового класса, например «площадь фигуры» с реализацией по умолчанию, как если бы это был квадрат. Цель CRTP в этом случае состоит в том, чтобы разрешить наиболее производную реализацию, «область трапеции» и т. д., Сохраняя при этом возможность ссылаться на трапецию как на форму до тех пор, пока не потребуется производное поведение. По сути, всякий раз, когда вам обычно понадобится dynamic_cast или виртуальные методы.
Есть два пути.
Первый заключается в статическом указании интерфейса для структуры типов:
template <class Derived>
struct base {
void foo() {
static_cast<Derived *>(this)->foo();
};
};
struct my_type : base<my_type> {
void foo(); // required to compile.
};
struct your_type : base<your_type> {
void foo(); // required to compile.
};
Второй - отказ от использования идиомы «ссылка на базу» или «указатель на базу» и выполнение связывания во время компиляции. Используя приведенное выше определение, вы можете иметь функции шаблона, которые выглядят следующим образом:
template <class T> // T is deduced at compile-time
void bar(base<T> & obj) {
obj.foo(); // will do static dispatch
}
struct not_derived_from_base { }; // notice, not derived from base
// ...
my_type my_instance;
your_type your_instance;
not_derived_from_base invalid_instance;
bar(my_instance); // will call my_instance.foo()
bar(your_instance); // will call your_instance.foo()
bar(invalid_instance); // compile error, cannot deduce correct overload
Таким образом, сочетание определения структуры / интерфейса и вывода типа во время компиляции в ваших функциях позволяет выполнять статическую отправку вместо динамической. В этом суть статического полиморфизма.
Я хотел бы подчеркнуть, что not_derived_from_base не является производным от base и не является производным от base ...
На самом деле объявление foo () внутри my_type / your_type не требуется. codepad.org/ylpEm1up (вызывает переполнение стека) - есть ли способ обеспечить определение foo во время компиляции? - Хорошо, нашел решение: ideone.com/C6Oz9 - Возможно, вы захотите исправить это в своем ответе.
Не могли бы вы объяснить мне, какова мотивация использования CRTP в этом примере? Если бы bar был определен как template <class T> void bar (T & obj) {obj.foo (); }, тогда подойдет любой класс, предоставляющий foo. Итак, исходя из вашего примера, похоже, что единственное использование CRTP - это указать интерфейс во время компиляции. Это то, для чего это нужно?
@Dean Michael Действительно, код в примере компилируется, даже если foo не определено в my_type и your_type. Без этих переопределений рекурсивно вызывается base :: foo (и stackoverflows). Так что, может быть, вы хотите исправить свой ответ, как показал cooky451?
@mezhaka: Да, пример Дина Майкла неполный, потому что, как вы показываете, его можно было бы более кратко реализовать без CRTP. Но добавьте template<class T> bar(base2<T> &obj) { obj.quux(); } - то есть второй базовый класс с другой реализацией bar() - и полезность CRTP станет очевидной.
Статическая рассылка CRTP / SFINAE со строгой проверкой подписи
Это решение для статической диспетчеризации использует CRTP и SFINAE, что не ново. Уникальность этого решения в том, что оно также требует строгой подписи проверка, которая позволяет нам статически отправлять перегруженные методы в один и тот же способ динамической диспетчеризации работает для виртуальных функций.
Для начала давайте сначала посмотрим на ограничения традиционного решения, использующего СФИНАЕ. Следующее было взято из выступления Бена Дина на конференции CppCon 2016 Lightning Talk. «Статическая альтернатива виртуальным функциям, использующая выражение SFINAE».
#define SFINAE_DETECT(name, expr) \
template <typename T> \
using name##_t = decltype(expr); \
template <typename T, typename = void> \
struct has_##name : public std::false_type {}; \
template <typename T> \
struct has_##name<T, void_t<name##_t<T>>> : public std::true_type {};
// detect CommonPrefix(string)
SFINAE_DETECT(common_prefix,
declval<T>().CommonPrefix(std::string()))
Используя приведенный выше код, создание экземпляра шаблона has_complete<DerivedClass>
будет, в общем, делать то, что вы ожидаете. Если DerivedClass имеет метод с именем
Complete, который принимает std::string, результирующий тип будет
std::true_type.
Что происходит, когда вы хотите перегрузить функцию?
template <class Derived>
struct Base {
std::string foo(bool);
std::string foo(int);
...
};
struct Derived : public Base<Derived>
{
std::string foo(int);
};
В этом случае у Derived действительно есть метод с именем foo, который принимает
bool, потому что bool неявно конвертируется в int. Следовательно,
даже если мы настроим отправку только для подписи, которая принимает логическое значение, has_foo<Derived> будет преобразован в std::true_type, и вызов будет
отправлено на Derived::foo(int). Мы этого хотим? Наверное, нет, потому что
виртуальные функции работают не так. Функция может только переопределить
виртуальная функция, если две подписи точно совпадают. Предлагаю провести
статический механизм диспетчеризации, который ведет себя таким же образом.
template <template <class...> class Op, class... Types>
struct dispatcher;
template <template <class...> class Op, class T>
struct dispatcher<Op, T> : std::experimental::detected_t<Op, T> {};
template <template <class...> class Op, class T, class... Types>
struct dispatcher<Op, T, Types...>
: std::experimental::detected_or_t<
typename dispatcher<Op, Types...>::type, Op, T> {};
template <template <class...> class Op, class... Types>
using dispatcher_t = typename dispatcher<Op, Types...>::type;
Это хорошо, но одно это не требует проверки подписи. Выполнить строгий
проверка подписи, мы должны правильно определить параметр шаблона шаблона
Op. Для этого мы воспользуемся std::integral_constant члена
указатель на функцию. Вот как это выглядит:
template <class T>
using foo_op_b = std::integral_constant<std::string(T::*)(bool), &T::foo>;
template <class T>
using foo_op_i = std::integral_constant<std::string(T::*)(int), &T::foo>
Определение наших Op таким образом позволяет нам отправлять только методы с
точное совпадение подписи.
// Resolves to std::integral_constant<std::string(T::*)(bool), &Derived::foo>
using foo_bool_ic = dispatcher_t<foo_op_b, Derived, Defaults>;
// Resolves to std::integral_constant<std::string(T::*)(int), &Defaults::foo>
using foo_int_ic = dispatcher_t<foo_op_i, Derived, Defaults>;
А теперь давайте все вместе.
#include <iostream>
#include <experimental/type_traits>
#include <string>
template <template <class...> class Op, class... Types>
struct dispatcher;
template <template <class...> class Op, class T>
struct dispatcher<Op, T> : std::experimental::detected_t<Op, T> {};
template <template <class...> class Op, class T, class... Types>
struct dispatcher<Op, T, Types...>
: std::experimental::detected_or_t<
typename dispatcher<Op, Types...>::type, Op, T> {};
template <template <class...> class Op, class... Types>
using dispatcher_t = typename dispatcher<Op, Types...>::type;
// Used to deduce class type from a member function pointer
template <class R, class T, class... Args>
auto method_cls(R(T::*)(Args...)) -> T;
struct Defaults {
std::string foo(bool value) { return value ? "true" : "false"; }
std::string foo(int value) { return value ? "true" : "false"; }
// Ensure that the class is polymorphic so we can use dynamic_cast
virtual ~Defaults() {};
};
template <class Derived>
struct Base : Defaults {
template <class T>
using foo_op_b = std::integral_constant<std::string(T::*)(bool), &T::foo>;
template <class T>
using foo_op_i = std::integral_constant<std::string(T::*)(int), &T::foo>;
std::string foo(bool value) {
auto method = dispatcher_t<foo_op_b, Derived, Defaults>::value;
auto *target = dynamic_cast<decltype(method_cls(method)) *>(this);
return (target->*method)(value);
}
std::string foo(int value) {
auto method = dispatcher_t<foo_op_i, Derived, Defaults>::value;
auto *target = dynamic_cast<decltype(method_cls(method)) *>(this);
return (target->*method)(value);
}
};
struct Derived : Base<Derived> {
std::string foo(bool value) { return value ? "TRUE" : "FALSE"; }
};
int main() {
Derived d;
std::cout << dynamic_cast<Base<Derived> *>(&d)->foo(true) << std::endl; // TRUE
std::cout << dynamic_cast<Base<Derived> *>(&d)->foo(1) << std::endl; // true
}
Написание макроса, который создает диспетчер для неперегруженной функции-члена было бы достаточно просто, но создание такого, поддерживающего перегруженные функции, быть немного сложнее. Если кто-то захочет внести свой вклад, я буду приветствовать добавление. В противном случае вот макрос для неперегруженных методов
Фактически, дублирование Base для экземпляра шаблона является иллюзией, потому что (если у вас еще нет vtable) компилятор объединит хранилище базы и производной в единую структуру для вас. Вызов указателя функции также оптимизируется компилятором (часть static_cast).