2010-05-20 5 views
24

Considérons la classe de modèle suivantspécifier des paramètres du modèle lors de l'exécution

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) 
} 

Les paramètres modèle P1, P2, P3 sont dans une gamme restreinte de 0 comme une certaine valeur fixe n fixe au moment de la compilation.

Maintenant, je voudrais construire une méthode « usine » comme

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

La question serait de savoir comment parvenir à la construction de la classe de modèle lorsque les paramètres du modèle ne sont connus que lors de l'exécution. Et serait le même possible avec des paramètres de modèle ayant un très grand domaine (comme un double)? S'il vous plaît considérer également, si la solution possible est extensible à l'utilisation de plus de paramètres de modèle.

+0

Je voudrais vraiment savoir la raison au-delà de cette question. Pourriez-vous nous expliquer ce que vous essayez d'accomplir en utilisant cette construction étrange? –

+1

Il existe un énorme algorithme qui peut être paramétré à l'aide de paramètres de type entier. En fonction des paramètres, la compilation génère du code hautement optimisé. Maintenant, je veux pouvoir utiliser ces différentes "versions" de l'extérieur sans se soucier de leur implémentation et en spécifiant les paramètres à l'exécution d'une manière supervisée par l'utilisateur. Malgré cette application, il s'agissait également d'une question théorique par pure curiosité. – Danvil

+0

Notez qu'en raison de l'instanciation d'un nombre potentiellement important de spécialisations, l'énorme taille exécutable qui en résulte peut techniquement aller à l'encontre de vos optimisations en termes de performances. Un gros code signifie souvent un code lent, en particulier en présence de motifs de branchement irréguliers. (comme toujours, profil pour savoir ce qui se passe) –

Répondre

17

Voici ce que vous pouvez faire:

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; 
} 

Notez que cela n'échelle même pas à distance à des valeurs à virgule flottante. Il échelles seulement à une liste connue de valeurs discrètes.


J'ai aussi utilisé ce morceau de code avant de faire des modèles de génération automatique:

#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 

Le compilateur produit une sortie qui ressemble à ceci:

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... 

Notez également qu'avec cette méthode, avec 4 variables, chacune comprenant plus de 13 valeurs, vous devez instancier le compilateur 28561 copies de cette fonction. Si votre n était de 50 et que vous aviez encore 4 options, vous auriez 6250000 fonctions instanciées. Cela peut faire pour une compilation SLOW.

+0

Merci, votre réponse est vraiment utile! – Danvil

+5

'Cela peut faire pour une compilation SLOW' - Sans parler d'une taille pré-traitée approchant un demi-gigaoctet et une taille exécutable massive si vous avez déjà trouvé un compilateur qui pourrait faire face à cela. –

+0

@Joe: Absolument. Je l'ai utilisé pour un ensemble de bools que je voulais essayer de modéliser. Je pense que le plus que j'ai jamais fait était <100 instantiations générées. –

8

Ce n'est pas possible, les modèles sont instanciés au moment de la compilation.
Au moment où vous avez un exécutable, vous avez seulement des classes (instanciations particulières de ces modèles), pas de modèles plus.

Si vous ne connaissez pas les valeurs lors de la compilation, vous ne pouvez pas avoir de modèles pour ceux-ci.

+0

Ce n'est pas vrai. Pour les paramètres entiers avec un petit domaine, on peut utiliser les instructions switch/if, comme indiqué dans le post par sharth. – Danvil

+0

Il n'y a aucun moyen de 'réaliser la construction de la classe template lorsque les paramètres du template ne sont connus que lors de l'exécution' sans construire tous les cas possibles et faire la commutation à l'exécution. –

+0

Et la question était comment faire par exemple cette commutation récursive (sans l'écrire dans le code à la main). – Danvil

0

Vous ne pouvez pas. modèle sont le temps de compilation seulement.

Vous pouvez générer au moment de la compilation toutes les valeurs de modèles possibles que vous voulez, et en choisir une lors de l'exécution.

2

C'est techniquement * possible ** - mais ce n'est pas pratique et c'est certainement la mauvaise façon d'aborder le problème.

Y a-t-il une raison pour laquelle P1, P2 et P3 ne peuvent pas être des variables entières normales?


* Vous pouvez incorporer un compilateur C++ et une copie de votre source, puis compiler une bibliothèque dynamique ou objet partagé qui implémente votre fonction d'usine pour un ensemble donné de P1, P2, P3 - mais voulez-vous vraiment pour faire ça? OMI, c'est une chose absolument folle à faire.

10

Si macros ne sont pas votre truc, vous pouvez également générer if-then-else en utilisant les modèles de:

#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); 
} 

Étant donné que les tests sont imbriqués, il devrait être plus efficace que la solution macro sharth.

Vous pouvez l'étendre à plus de paramètres en ajoutant plus de cas de profondeur.

+0

C'est difficile: D – Danvil

+0

TMP est souvent ... –

1

trop tard, je sais, mais qu'en est-ce:

// 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

Je ne sais pas si cela est applicable à votre problème actuel, mais il semblerait que 11 constexpr C++ peut être ce que vous cherchez -fonctions peuvent être appelées pendant l'exécution et en même temps peuvent être exécutées au moment de la compilation. L'utilisation de constexpr a aussi l'avantage d'être bien plus "propre" que d'utiliser TMP, de travailler avec des valeurs d'exécution (pas seulement des valeurs entières) tout en conservant la plupart des avantages de TMP tels que la mémoisation et l'exécution du temps de compilation. ceci est quelque peu donné à la décision du compilateur. En fait,est généralement beaucoup plus rapide qu'une version équivalente à TMP. Notez également qu'en général, l'utilisation de templates pendant l'exécution nuirait à l'une des plus grandes caractéristiques du template - Le fait qu'ils soient manipulés pendant la compilation et disparaissent pratiquement pendant l'exécution.

+1

Exemple s'il vous plaît? – Andrew

Questions connexes