2010-05-20 2 views
24

Рассмотрим следующий класс шаблона:Укажите параметры шаблона во время выполнения

class MyClassInterface { 
public: 
    virtual double foo(double) = 0; 
} 

class MyClass<int P1, int P2, int P3> 
: public MyClassInterface { 
public: 
    double foo(double a) { 
    // complex computation dependent on P1, P2, P3 
    } 
    // more methods and fields (dependent on P1, P2, P3) 
} 

параметры P1 Шаблон, P2, P3 находятся в ограниченном диапазоне, как от 0 до некоторого фиксированного значения n фиксированной во время компиляции.

Теперь я хотел бы построить метод «фабрики», как

MyClassInterface* Factor(int p1, int p2, int p3) { 
    return new MyClass<p1,p2,p3>(); // <- how to do this? 
} 

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

+0

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

+1

Существует огромный алгоритм, который можно параметризовать с использованием параметров целочисленного шаблона. В зависимости от параметров компилятор генерирует некоторый высоко оптимизированный код. Теперь я хочу иметь возможность использовать эти разные «версии» извне, не заботясь об их реализации и задавая параметры во время выполнения под контролем пользователя. Несмотря на это приложение, это также было теоретическим вопросом из чистого любопытства. – Danvil

+0

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

ответ

17

Вот что вы можете сделать:

MyClassInterface* Factor(int p1, int p2, int p3) { 
    if (p1 == 0 && p2 == 0 && p3 == 0) 
    return new MyClass<0,0,0>(); 
    if (p1 == 0 && p2 == 0 && p3 == 1) 
    return new MyClass<0,0,1>(); 
    etc; 
} 

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


Я также использовал этот кусок кода, прежде чем сделать какой-то шаблон автоматической генерации:

#include <boost/preprocessor.hpp> 

#define RANGE ((0)(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)) 
#define MACRO(r, p) \ 
    if (BOOST_PP_SEQ_ELEM(0, p) == var1 && BOOST_PP_SEQ_ELEM(1, p) == var2 && BOOST_PP_SEQ_ELEM(2, p) == var3 && BOOST_PP_SEQ_ELEM(3, p) == var4) \ 
     actual_foo = foo<BOOST_PP_TUPLE_REM_CTOR(4, BOOST_PP_SEQ_TO_TUPLE(p))>; 
BOOST_PP_SEQ_FOR_EACH_PRODUCT(MACRO, RANGE RANGE RANGE RANGE) 
#undef MACRO 
#undef RANGE 

компилятор производит вывод, который выглядит следующим образом:

if (0 == var1 && 0 == var2 && 0 == var3 && 0 == var4) actual_foo = foo<0, 0, 0, 0>; 
if (0 == var1 && 0 == var2 && 0 == var3 && 1 == var4) actual_foo = foo<0, 0, 0, 1>; 
if (0 == var1 && 0 == var2 && 0 == var3 && 2 == var4) actual_foo = foo<0, 0, 0, 2>; 
if (0 == var1 && 0 == var2 && 0 == var3 && 3 == var4) actual_foo = foo<0, 0, 0, 3>; 
if (0 == var1 && 0 == var2 && 0 == var3 && 4 == var4) actual_foo = foo<0, 0, 0, 4>; 
if (0 == var1 && 0 == var2 && 0 == var3 && 5 == var4) actual_foo = foo<0, 0, 0, 5>; 
if (0 == var1 && 0 == var2 && 0 == var3 && 6 == var4) actual_foo = foo<0, 0, 0, 6>; 
if (0 == var1 && 0 == var2 && 0 == var3 && 7 == var4) actual_foo = foo<0, 0, 0, 7>; 
if (0 == var1 && 0 == var2 && 0 == var3 && 8 == var4) actual_foo = foo<0, 0, 0, 8>; 
etc... 

Кроме того, обратите внимание, что с помощью этого метода, с 4 переменными, каждый из которых содержит более 13 значений, вы должны заставить компилятор создать экземпляр 28561 копий этой функции. Если вашему n было 50, и у вас все еще было 4 варианта, вы бы создали 6250000 функций. Это может сделать для компиляции SLOW.

+0

Спасибо, ваш ответ действительно полезен! – Danvil

+5

«Это может сделать для SLOW-компиляции» - не говоря уже о предварительно обработанном размере, приближающемся к половине гигабайта, и массивном конечном исполняемом размере, если вы когда-либо находили компилятор, который мог бы справиться с этим. –

+0

@ Joe: Абсолютно. Я использовал это для набора bools, который я хотел бы попробовать запланировать. Я думаю, что большинство из тех, что я когда-либо делал, составляли 100 экземпляров. –

8

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

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

+0

Это неправда. Для целочисленных параметров с небольшим доменом можно использовать операторы switch/if, как указано в сообщении sharth. – Danvil

+0

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

+0

И вопрос в том, как, например, выполнить эту рекурсивную переключение (не записывая ее вручную). – Danvil

0

Вы не можете. шаблон - это время компиляции.

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

2

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

Есть ли причина, по которой P1, P2 и P3 не могут быть правильными целыми переменными?


* Вы можете встроить ++ компилятор C и копию источника, а затем скомпилировать динамическую библиотеку или разделяемый объект, который реализует вашу фабричную функцию для данного набора P1, P2, P3 - но действительно ли вы хотите сделать это? ИМО, это абсолютно безумная вещь.

10

Если макросы не ваша вещь, то вы также можете сгенерировать если-то-иначе, используя шаблоны в:

#include <stdexcept> 
#include <iostream> 

const unsigned int END_VAL = 10; 

class MyClassInterface 
{ 
public: 
    virtual double foo (double) = 0; 
}; 

template<int P1, int P2, int P3> 
class MyClass : public MyClassInterface 
{ 
public: 
    double foo (double a) 
    { 
     return P1 * 100 + P2 * 10 + P3 + a; 
    } 
}; 

struct ThrowError 
{ 
    static inline MyClassInterface* create (int c1, int c2, int c3) 
    { 
     throw std::runtime_error ("Could not create MyClass"); 
    } 
}; 

template<int DEPTH = 0, int N1 = 0, int N2 = 0, int N3 = 0> 
struct Factory : ThrowError {}; 

template<int N2, int N3> 
struct Factory<0, END_VAL, N2, N3> : ThrowError {}; 

template<int N1, int N3> 
struct Factory<1, N1, END_VAL, N3> : ThrowError {}; 

template<int N1, int N2> 
struct Factory<2, N1, N2, END_VAL> : ThrowError {}; 

template<int N1, int N2, int N3> 
struct Factory<0, N1, N2, N3> 
{ 
    static inline MyClassInterface* create (int c1, int c2, int c3) 
    { 
     if (c1 == N1) 
     { 
      return Factory<1, N1, 0, 0>::create (c1, c2, c3); 
     } 
     else 
      return Factory<0, N1 + 1, N2, N3>::create (c1, c2, c3); 
    } 
}; 

template<int N1, int N2, int N3> 
struct Factory<1, N1, N2, N3> 
{ 
    static inline MyClassInterface* create (int c1, int c2, int c3) 
    { 
     if (c2 == N2) 
     { 
      return Factory<2, N1, N2, 0>::create (c1, c2, c3); 
     } 
     else 
      return Factory<1, N1, N2 + 1, N3>::create (c1, c2, c3); 
    } 
}; 

template<int N1, int N2, int N3> 
struct Factory<2, N1, N2, N3> 
{ 
    static inline MyClassInterface* create (int c1, int c2, int c3) 
    { 
     if (c3 == N3) 
     { 
      return new MyClass<N1, N2, N3>(); 
     } 
     else 
      return Factory<2, N1, N2, N3 + 1>::create (c1, c2, c3); 
    } 
}; 

MyClassInterface* factory (int c1, int c2, int c3) 
{ 
    return Factory<>::create (c1, c2, c3); 
} 

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

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

+0

Это сложно: D – Danvil

+0

TMP часто бывает ... –

1

слишком поздно, я знаю, но что об этом:

// MSVC++ 2010 SP1 x86 
// boost 1.53 

#include <tuple> 
#include <memory> 
// test 
#include <iostream> 

#include <boost/assert.hpp> 
#include <boost/static_assert.hpp> 
#include <boost/mpl/size.hpp> 
#include <boost/mpl/vector.hpp> 
#include <boost/mpl/push_back.hpp> 
#include <boost/mpl/pair.hpp> 
#include <boost/mpl/begin.hpp> 
#include <boost/mpl/deref.hpp> 
#include <boost/mpl/int.hpp> 
#include <boost/mpl/placeholders.hpp> 
#include <boost/mpl/unpack_args.hpp> 
#include <boost/mpl/apply.hpp> 
// test 
#include <boost/range/algorithm/for_each.hpp> 

/*! \internal 
*/ 
namespace detail 
{ 
/*! \internal 
*/ 
namespace runtime_template 
{ 

/*! \internal 
    fwd 
*/ 
template < 
    typename Template 
    , typename Types 
    , typename Map // top level map iterator 
    , typename LastMap // top level map iterator 
    , int Index 
    , bool Done = std::is_same<Map, LastMap>::value 
> 
struct apply_recursive_t; 

/*! \internal 
    fwd 
*/ 
template < 
    typename Template 
    , typename Types 
    , typename Map // top level map iterator 
    , typename LastMap // top level map iterator 
    , typename First 
    , typename Last 
    , int Index 
    , bool Enable = !std::is_same<First, Last>::value 
> 
struct apply_mapping_recursive_t; 

/*! \internal 
    run time compare key values + compile time push_back on \a Types 
*/ 
template < 
    typename Template 
    , typename Types 
    , typename Map // top level map iterator 
    , typename LastMap // top level map iterator 
    , typename First 
    , typename Last 
    , int Index // current argument 
    , bool Enable /* = !std::is_same<First, Last>::value */ 
> 
struct apply_mapping_recursive_t 
{ 
    typedef void result_type; 
    template <typename TypeIds, typename T> 
    inline static void apply(const TypeIds& typeIds, T&& t) 
    { namespace mpl = boost::mpl; 
     typedef typename mpl::deref<First>::type key_value_pair; 
     typedef typename mpl::first<key_value_pair>::type typeId; // mpl::int 
     if (typeId::value == std::get<Index>(typeIds)) 
     { 
      apply_recursive_t< 
       Template 
       , typename mpl::push_back< 
        Types 
        , typename mpl::second<key_value_pair>::type 
       >::type 
       , typename mpl::next<Map>::type 
       , LastMap 
       , Index + 1 
      >::apply(typeIds, std::forward<T>(t)); 
     } 
     else 
     { 
      apply_mapping_recursive_t< 
       Template 
       , Types 
       , Map 
       , LastMap 
       , typename mpl::next<First>::type 
       , Last 
       , Index 
      >::apply(typeIds, std::forward<T>(t)); 
     } 
    } 
}; 

/*! \internal 
    mapping not found 
    \note should never be invoked, but must compile 
*/ 
template < 
    typename Template 
    , typename Types 
    , typename Map // top level map iterator 
    , typename LastMap // top level map iterator 
    , typename First 
    , typename Last 
    , int Index 
> 
struct apply_mapping_recursive_t< 
    Template 
    , Types 
    , Map 
    , LastMap 
    , First 
    , Last 
    , Index 
    , false 
> 
{ 
    typedef void result_type; 
    template <typename TypeIds, typename T> 
    inline static void apply(const TypeIds& /* typeIds */, T&& /* t */) 
    { 
     BOOST_ASSERT(false); 
    } 
}; 

/*! \internal 
    push_back on \a Types template types recursively 
*/ 
template < 
    typename Template 
    , typename Types 
    , typename Map // top level map iterator 
    , typename LastMap // top level map iterator 
    , int Index 
    , bool Done /* = std::is_same<Map, LastMap>::value */ 
> 
struct apply_recursive_t 
{ 
    typedef void result_type; 
    template <typename TypeIds, typename T> 
    inline static void apply(const TypeIds& typeIds, T&& t) 
    { namespace mpl = boost::mpl; 
     typedef typename mpl::deref<Map>::type Mapping; // [key;type] pair vector 
     apply_mapping_recursive_t< 
      Template 
      , Types 
      , Map 
      , LastMap 
      , typename mpl::begin<Mapping>::type 
      , typename mpl::end<Mapping>::type 
      , Index 
     >::apply(typeIds, std::forward<T>(t)); 
    } 
}; 

/*! \internal 
    done! replace mpl placeholders of \a Template with the now complete \a Types 
    and invoke result 
*/ 
template < 
    typename Template 
    , typename Types 
    , typename Map 
    , typename LastMap 
    , int Index 
> 
struct apply_recursive_t< 
    Template 
    , Types 
    , Map 
    , LastMap 
    , Index 
    , true 
> 
{ 
    typedef void result_type; 
    template <typename TypeIds, typename T> 
    inline static void apply(const TypeIds& /* typeIds */, T&& t) 
    { namespace mpl = boost::mpl; 
     typename mpl::apply< 
      mpl::unpack_args<Template> 
      , Types 
     >::type()(std::forward<T>(t)); 
    } 
}; 

/*! \internal 
    helper functor to be used with invoke_runtime_template() 
    \note cool: mpl::apply works with nested placeholders types! 
*/ 
template <typename Template> 
struct make_runtime_template_t 
{ 
    typedef void result_type; 
    template <typename Base> 
    inline void operator()(std::unique_ptr<Base>* base) const 
    { 
     base->reset(new Template()); 
    } 
}; 

} // namespace runtime_template 
} // namespace detail 

/*! \brief runtime template parameter selection 

    \param Template functor<_, ...> placeholder expression 
    \param Maps mpl::vector<mpl::vector<mpl::pair<int, type>, ...>, ...> 
    \param Types std::tuple<int, ...> type ids 
    \param T functor argument type 

    \note all permutations must be compilable (they will be compiled of course) 
    \note compile time: O(n!) run time: O(n) 

    \sa invoke_runtime_template() 
    \author slow 
*/ 
template < 
    typename Template 
    , typename Map 
    , typename Types 
    , typename T 
> 
inline void invoke_runtime_template(const Types& types, T&& t) 
{ namespace mpl = boost::mpl; 
    BOOST_STATIC_ASSERT(mpl::size<Map>::value == std::tuple_size<Types>::value); 
    detail::runtime_template::apply_recursive_t< 
     Template 
     , mpl::vector<> 
     , typename mpl::begin<Map>::type 
     , typename mpl::end<Map>::type 
     , 0 
    >::apply(types, std::forward<T>(t)); 
} 

/*! \sa invoke_runtime_template() 
*/ 
template < 
    typename Template 
    , typename Map 
    , typename Base 
    , typename Types 
> 
inline void make_runtime_template(const Types& types, std::unique_ptr<Base>* base) 
{ 
    invoke_runtime_template< 
     detail::runtime_template::make_runtime_template_t<Template> 
     , Map 
    >(types, base); 
} 

/*! \overload 
*/ 
template < 
    typename Base 
    , typename Template 
    , typename Map 
    , typename Types 
> 
inline std::unique_ptr<Base> make_runtime_template(const Types& types) 
{ 
    std::unique_ptr<Base> result; 

    make_runtime_template<Template, Map>(types, &result); 
    return result; 
} 

//////////////////////////////////////////////////////////////////////////////// 

namespace mpl = boost::mpl; 
using mpl::_; 

class MyClassInterface { 
public: 
    virtual ~MyClassInterface() {} 
    virtual double foo(double) = 0; 
}; 

template <int P1, int P2, int P3> 
class MyClass 
: public MyClassInterface { 
public: 
    double foo(double /*a*/) { 
     // complex computation dependent on P1, P2, P3 
     std::wcout << typeid(MyClass<P1, P2, P3>).name() << std::endl; 
     return 42.0; 
    } 
    // more methods and fields (dependent on P1, P2, P3) 
}; 

// wrapper for transforming types (mpl::int) to values 
template <typename P1, typename P2, typename P3> 
struct MyFactory 
{ 
    inline void operator()(std::unique_ptr<MyClassInterface>* result) const 
    { 
     result->reset(new MyClass<P1::value, P2::value, P3::value>()); 
    } 
}; 

template <int I> 
struct MyConstant 
    : boost::mpl::pair< 
     boost::mpl::int_<I> 
     , boost::mpl::int_<I> 
    > {}; 

std::unique_ptr<MyClassInterface> Factor(const std::tuple<int, int, int>& constants) { 
    typedef mpl::vector< 
     MyConstant<0> 
     , MyConstant<1> 
     , MyConstant<2> 
     , MyConstant<3> 
     // ... 
    > MyRange; 
    std::unique_ptr<MyClassInterface> result; 
    invoke_runtime_template< 
     MyFactory<_, _, _> 
     , mpl::vector<MyRange, MyRange, MyRange> 
    >(constants, &result); 
    return result; 
} 

int main(int /*argc*/, char* /*argv*/[]) 
{ 
    typedef std::tuple<int, int, int> Tuple; 
    const Tuple Permutations[] = 
    { 
     std::make_tuple(0,  0, 0) 
     , std::make_tuple(0, 0, 1) 
     , std::make_tuple(0, 1, 0) 
     , std::make_tuple(0, 1, 1) 
     , std::make_tuple(1, 0, 0) 
     , std::make_tuple(1, 2, 3) 
     , std::make_tuple(1, 1, 0) 
     , std::make_tuple(1, 1, 1) 
     // ... 
    }; 

    boost::for_each(Permutations, [](const Tuple& constants) { Factor(constants)->foo(42.0); }); 
    return 0; 
} 
2

Я не знаю, если это применимо к вашей текущей проблеме, но казалось бы, что 11 constexpr C++ может быть то, что вы ищете - constexpr функции могут вызываться во время выполнения и в то же время могут выполняться во время компиляции.

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

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

+1

Пример, пожалуйста? – Andrew

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