代码之家  ›  专栏  ›  技术社区  ›  Danvil

在运行时指定模板参数

  •  27
  • Danvil  · 技术社区  · 14 年前

    考虑以下模板类

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

    问题是,当模板参数仅在运行时已知时,如何实现模板类的构造。如果模板参数具有非常大的域(如双精度域),是否也可以这样做?如果可能的解决方案可以扩展到使用更多的模板参数,也请考虑。

    7 回复  |  直到 10 年前
        1
  •  17
  •   Bill Lynch    14 年前

    您可以这样做:

    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个函数被实例化。这会导致编译速度变慢。

        2
  •  10
  •   jon hanson    14 年前

    如果宏不是您的东西,那么您也可以使用模板生成if-then-else:

    #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的宏解决方案更有效。

    通过添加更多的深度情况,可以将其扩展到更多参数。

        3
  •  8
  •   Arkaitz Jimenez    14 年前

    这是不可能的,模板是在编译时实例化的。
    当您有一个可执行文件时,您只有类(这些模板的特定实例化),不再有模板了。

    如果您在编译时不知道值,那么就不能为这些值设置模板。

        4
  •  2
  •   JoeG    14 年前

    从技术上讲,这是可能的,但这是不实际的,而且几乎可以肯定这是解决问题的错误方法。

    为什么p1、p2和p3不能是正整数变量?


    *你可以嵌入一个C++编译器和一个源的拷贝,然后编译一个动态库或共享对象,它为给定的P1、P2、P3集实现你的工厂函数-但是你真的想这么做吗?在我看来,这绝对是一件疯狂的事情。

        5
  •  2
  •   user2008934    10 年前

    我不知道这是否适用于你当前的问题,但是它看起来是C++ 11。 constexpr 可能是你想要的- 常量表达式 函数可以在运行时调用,同时也可以在编译时执行。

    使用 常量表达式 与使用tmp相比,使用任何运行时值(不仅仅是整数值),同时保留tmp的大部分优点(如memoization和编译时执行),尽管这在一定程度上取决于编译器的决定,但它还有一个额外的好处,那就是它看起来比使用tmp更“干净”。事实上, 常量表达式 通常比TMP等效版本快得多。

    还要注意的是,在运行时使用模板通常会破坏模板的一个最大特性,即在编译时处理模板,在运行时几乎消失。

        6
  •  1
  •   brickner    14 年前

    不能。模板只能在编译时使用。

    您可以在编译时构建所有可能的模板值,并在运行时选择其中一个值。

        7
  •  0
  •   slow    11 年前

    太晚了,我知道,但是这个呢:

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