2017-01-12 14 views
1

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

Я строю библиотеку, которая содержит иерархию шаблонных классов. Я хочу создать массив «фабричных» функций, которые могут использоваться для создания производных классов по имени (например, на основе параметров командной строки).

Если возможно, я хочу, чтобы каждый производный класс мог зарегистрировать себя в своем собственном файле .hpp или .cpp (в отличие от необходимости поддерживать один список всех возможных производных классов где-нибудь).

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

// 
// This code would appear in a library 
// 

template<class T> 
class Base { 
public: 
    Base(const char* /*param*/) {} 
}; 

// 
// Description of each derived class. 
// We need this non-templated base class so we can store all our 
// descriptions in a single vector 
// 
class DescriptionBase { 

private: 
    const char *description; 
    const char *name; 

public: 
    DescriptionBase(const char* pDesc, const char* pName) : description(pDesc), name(pName){ 
    // Whenever a Description object is created, it is automatically registered with the 
    // global descriptionList. This allows us to register derived classes in their own 
    // .cpp/.hpp file (as opposed to keeping a centralized list of all derived classes). 
    descriptionList.push_back(this); 
    } 

    // FAIL Can't have virtual template functions 
    virtual template<class T> 
    Base<T> *make(const char *param) {return new Base<T>(param); } 

    static vector<DescriptionBase *> descriptionList; 
}; 

//global list of all derived classes 
vector<DescriptionBase *> DescriptionBase::descriptionList; 

// We use the template to store the type of the derived class 
// for use in the make method 
template<template<typename> class D> 
class Description : public DescriptionBase { 

public: 
    Description(const char* pDesc, const char* pName) : DescriptionBase(pDesc, pName) {} 

    template<class T> 
    Base<T> *make(const char *params) { 
    return new D<T>(params); 
    } 
}; 

// 
// These derived classes may be part of the library, or they may be 
// written by users of the library. 
// 


template<class T> 
class DerivedA : public Base<T> { 
public: 
    DerivedA(const char* param) : Base<T>(param) {return;} 
}; 

Description<DerivedA> derivedA("derivedA", "This is the first derived class"); 

template<class T> 
class DerivedB : public Base<T> { 
    DerivedB(const char* param) : Base<T>(param) {return;} 
}; 
Description<DerivedA> derivedB("derivedA", "This is the second derived class"); 

// 
// Example code written by the user of the library. 
// 
// 


int main(int argc, char *argv[]) { 

    // Using a descriptionList is just a short-cut here. 
    // Final code will use a map. 
    int indexOfDerivedA = 0; 

    Base<int> *intItem = DescriptionBase::descriptionList[indexOfDerivedA]->make<int>("parameter to derived type's constructor"); 
    Base<char> *charItem = DescriptionBase::descriptionList[indexOfDerivedA]->make<char>("parameter to derived type's constructor"); 


} 
+1

Если вы не знаете тип возвращаемого значения, то вы не можете использовать конструктор разумно, поэтому они либо должны иметь общий тип возврата (так что вы действительно можете его присвоить), либо они должны быть отдельно названные фабрики. Система типов не может знать, какой тип «fred» или «george», но он должен определить во время компиляции, если вызов действителен. Важно помнить, что Base и Base никак не связаны с перспективой системы типов. – xaxxon

+0

Я не читал ваш код, но вы можете сделать это, имея карту строк для функторов, которые возвращают объект, соответствующий интерфейсу. Классы регистрируют себя, вставляя на эту функцию функтор. – imreal

+0

@xaxxon Я изменил код, чтобы быть более понятным. «fred» и «george» были параметрами конструктора, а не именами типов. База и база не предназначены для связи. Я застрял в создании библиотеки 'descriptionList' в библиотеке (которая, конечно же, не знает, какие параметры шаблона выбирают пользователи). – Zack

ответ

2

Это время компиляции тег и тип:

template<class T> 
struct tag_t { constexpr tag_t() {}; }; 
template<class T> constexpr tag_t tag<T>{}; 

Это список типов:

template<class...>struct types_t{constexpr types_t(){}; using type=types_t;}; 
template<class...Ts>constexpr types_t<Ts...> types{}; 

Это отображает содержимое списка типов:

template<template<class...>class Z, class types> 
struct fmap{}; 
template<template<class...>class Z, class types> 
using fmap_t=typename fmap<Z,types>::type; 
template<template<class...>class Z, template<class...>class types, class...Ts> 
struct fmap<Z,types<Ts...>> { 
    using type=types<Z<Ts...>>; 
}; 

Теперь давайте составить список заводов:

template<class...Args> 
struct build_tagged_sig { 
    template<class T> 
    using result = std::unique_ptr<T>(tag_t<T>,Args...); 
}; 
template<template<class...>class Out, class types, class...Args> 
using tagged_factories = 
    fmap_t< 
    std::function, 
    fmap_t< 
     build_tagged_sig<Args...>::template result, 
     fmap_t< Out, types > 
    > 
    >; 

Это относится к types к шаблону:

template<template<class...>class Z, class types> 
struct apply_types {}; 
template<template<class...>class Z, class types> 
using apply_types_t = typename apply_types<Z,types>::type; 
template<template<class...>class Z, template<class...>class types, class...Ts> 
struct apply_types<Z, types<Ts...>> { 
    using type=Z<Ts...>; 
}; 
template<template<class...>class Z> 
struct applier { 
    template<class types> 
    using result=apply_types_t<Z,types>; 
}; 

This SO post shows how to overload multiple lambdas or std::functions.

using my_types = types_t< std::int8_t, std::int16_t, std::int32_t, std::int64_t >; 

using magic_factory = 
    apply_types_t< 
    overload, 
    tagged_factories< Base, my_types, const char* > > 
    >; 

который является перегрузка над

std::function< std::unique_ptr<Base<std::int8_t>>(tag_t<Base<std::int8_t>>, const char*) >, 
std::function< std::unique_ptr<Base<std::int16_t>>(tag_t<Base<std::int16_t>>, const char*) >, 
std::function< std::unique_ptr<Base<std::int32_t>>(tag_t<Base<std::int32_t>>, const char*) >, 
std::function< std::unique_ptr<Base<std::int64_t>>(tag_t<Base<std::int64_t>>, const char*) > 

теперь написать регистр завод:

template<class F, class...Ts> 
magic_factory make_poly_factory(types_t<Ts...>, F&& f) { 
    return magic_factory(
    (void(tag<Ts>), f)... 
); 
} 
template<class F> 
magic_factory make_poly_factory(F&& f) { 
    return make_poly_factory(my_types{}, f); 
} 

, который создает N копии f и сохраняет каждый в std::function все в одном объекте.

Принимая возвращаемое значение, вы можете вызвать индивидуальное разрешение при перегрузке.

template<class T> 
std::unique_ptr<Base<T>> factory_A_impl(tag_t<Base<T>>, const char* param) { 
    return new DerivedA<T>(param); 
} 
auto magic = magic_factory(my_types{}, [](auto tag, const char* param){ 
    return factory_A_impl(tag, param); 
}); 

std::unique_ptr<Base<std::int8_t>> bob = magic(tag<std::int8_t>, "hello"); 

и bob является unique_ptr к Base<std::int8_t>, что на самом деле в DerivedA<std::int8_t> во время выполнения.

У этого, вероятно, есть tpyos.

Большая часть этого сообщения была метапрограммированием для создания единственного объекта, который перегружает каждый из tag_t<T0> через tag_t<T1>, не повторяясь. Вы можете сделать это вручную для своих 4 типов.

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

Учащийся конечный пользователь просто должен создать функциональный объект, который принимает tag_t<X> и const char* и возвращает unique_ptr<X> и дешев к копии, а затем создать magic_factory от него (тип-стирает его вниз в пучок от std::function с).

DescriptionBase становится:

struct Description { 
    char const* description = 0; 
    char const* name = 0; 
    magic_factory factory; 
    template<class T> 
    std::unique_ptr<Base<T>> 
    make(const char *param) { 
    return factory(tag<T>, param); 
    } 
}; 

полиморфизм теперь в magic_factory. Храните экземпляры Description, а не указатели на них, поскольку они являются полиморфизмом типа значения.

См., Легко.

Добавление дополнительных параметров шаблона просто добавляет некоторую дополнительную сложность материалам fmap и больше ответственности перед создателем magic_factory.

Вам понадобится операция с несколькими продуктами для создания 64 различных наборов типов из одного списка из 4 элементов. Это будет types_ttypes_t.

Назовите это my_many_types.

Тогда

using magic_factory = 
    apply_types_t< 
    overload, 
    tagged_factories< applier<Base>::template result, my_types, const char* > > 
    >; 

и сделано, мы теперь имеем 64 перегруженные с подписями вроде:

std::unique_ptr<Base<std::int8_t, std::int16_t, std::int8_t>>(
    tag_t<Base<std::int8_t, std::int16_t, std::int8_t>>, 
    const char* 
) 

Теперь мы просто могли бы сделать все это вручную.

Построить таблицу, как это:

template<class T> 
using factory_ptr = std::unique_ptr<T>(void*, tag_t<T>, const char*); 

using factory_table = std::tuple< 
    factory_ptr< Base< std::int8_t, std::int8_t, std::int8_t > >, 
    factory_ptr< Base< std::int8_t, std::int8_t, std::int16_t > >, 
    factory_ptr< Base< std::int8_t, std::int8_t, std::int32_t > >, 
    factory_ptr< Base< std::int8_t, std::int8_t, std::int64_t > >, 
    factory_ptr< Base< std::int8_t, std::int16_t, std::int8_t > >, 
    factory_ptr< Base< std::int8_t, std::int16_t, std::int16_t > >, 
    factory_ptr< Base< std::int8_t, std::int16_t, std::int32_t > >, 
    factory_ptr< Base< std::int8_t, std::int16_t, std::int64_t > >, 

...

factory_ptr< Base< std::int64_t, std::int64_t, std::int64_t > > 
>; 

Теперь волшебная фабрика:

struct magic_factory { 
    std::unique_ptr<void, void(*)(void*)> state; 
    factory_table table; 

    template<class T0, class T1, class T2> 
    std::unique_ptr<Base<T0, T1, T2>> make(char const* param) { 
    auto f = std::get<factory_ptr< Base< T0, T1, T2 > >>(table); 
    return f(state.get(), param); 
    } 

    magic_factory(magic_factory&&)=default; 
    template<class T, 
    class=std::enable_if_t<!std::is_same<std::decay_t<T>, magic_factory>::value> 
    > 
    magic_factory(T&& t) { 
    ptr = {new std::decay_t<T> >(std::forward<T>(t)), 
     [](void* ptr){ 
     delete static_cast< std::decay_t<T>* >(ptr); 
     } 
    }; 
    // 64 lines like this: 
    std::get<factory_ptr< Base< std::int8_t, std::int8_t, std::int8_t > >>(table) 
    = +[](void* pvoid, tag_t<Base< std::int8_t, std::int8_t, std::int8_t >> tag, char const* param)->std::unique_ptr<Base< std::int8_t, std::int8_t, std::int8_t >> 
    { 
     auto*pt = static_cast<std::decay_t<T>*>(pvoid); 
     return (pt)(tag, param); 
    }; 
    } 
}; 

, где мы строим tuple функций типа стирания и указатель void на его аргумент и отправляйте сами.

Вы также можете использовать некоторые из перечисленных выше механизмов для автоматизации этого процесса. Ручная таблица действительно дает эффективность, поскольку мы не дублируем состояние вызываемого объекта N раз, как с версией std::function.


Другой подход заключается в использовании решения my type erasing type erasure.

Мы пишем набор шаблонов any_method s, которые выполняют трюк тэга для создания объекта Base<A,B,C>.

Затем мы создаем super_any на каждом из any_method.

Затем мы наследуем его и завершаем make, чтобы отправить их any_method.

Это может быть примерно так же эффективно, как и ручной подход чуть выше.

template<class T0, class T1, class T2> 
auto make_base = make_any_method<std::unique_ptr<Base<T0, T1, T2>>(char const* name)>(
    [](auto* p, char const* name) 
    { 
    return p->make<T0, T1, T2>(name); 
    } 
); 
template<class T0, class T1, class T2> 
using base_maker = decltype(make_base); 

Теперь наша фабрика:

super_any< base_maker<std::int8_t, std::int8_t, std::int8_t > > bob; 

bob может хранить указатель на класс, который реализует make<T0, T1, T2>, где они совпадают int8_t, int8_t, int8_t.

Добавить еще 64 линии и сделать.

Типы, которые bob магазины не обязательно имеют общий базовый класс. Он не использует наследование C++ для реализации полиморфизма, но вместо этого ручное стирание типа.

Назвать это просто

auto r = (bob->*make_base<std::int8_t, std::int8_t, std::int8_t>)("hello"); 

Естественно передать больше типов в super_any и Suppports больше типов make_base.

 Смежные вопросы

  • Нет связанных вопросов^_^