2010-05-20 41 views
24

Hãy xem xét các lớp mẫu sauXác định các thông số mẫu trong thời gian chạy

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

Mẫu thông số P1, P2, P3 đang ở trong một phạm vi giới hạn như từ 0 một số giá trị cố định n cố định tại thời gian biên dịch.

Bây giờ tôi muốn xây dựng một phương pháp "nhà máy" như

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

Câu hỏi đặt ra sẽ là như thế nào để đạt được việc xây dựng các lớp mẫu khi thông số mẫu chỉ được biết đến khi chạy. Và điều tương tự có thể xảy ra với các tham số mẫu có tên miền rất lớn (giống như một đôi)? Cũng hãy xem xét, nếu giải pháp có thể có thể mở rộng để sử dụng nhiều thông số mẫu hơn.

+0

Tôi thực sự muốn biết lý do vượt ra ngoài câu hỏi đó. Bạn có thể giải thích cho chúng tôi những gì bạn đang cố gắng đạt được bằng cách sử dụng cấu trúc kỳ lạ này không? –

+1

Có một thuật toán rất lớn có thể được tham số hóa bằng cách sử dụng các tham số nguyên mẫu. Phụ thuộc vào các tham số, biên dịch tạo ra một số mã được tối ưu hóa cao. Bây giờ tôi muốn có thể sử dụng những "phiên bản" khác nhau từ bên ngoài mà không quan tâm đến việc triển khai của chúng và bằng cách chỉ định các tham số khi chạy theo cách do người dùng giám sát. Mặc dù ứng dụng này, điều này cũng có nghĩa là một câu hỏi lý thuyết trong sự tò mò tinh khiết. – Danvil

+0

Lưu ý rằng do sự khởi tạo của một số lượng lớn các chuyên ngành có thể có, kích thước khổng lồ có thể thực thi được về mặt kỹ thuật có thể đi ngược lại với các tối ưu hóa của bạn một cách hiệu quả. Mã lớn thường có nghĩa là mã chậm, đặc biệt là trong sự hiện diện của các mẫu phân nhánh không đều. (như mọi khi, hồ sơ để biết những gì đang xảy ra) –

Trả lời

17

Đây là những gì bạn có thể làm:

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

Lưu ý rằng điều này không thậm chí từ xa mở rộng để các giá trị dấu chấm động. Nó chỉ quy mô cho một danh sách được biết đến của các giá trị rời rạc.


Tôi cũng đã sử dụng chút mã này trước khi thực hiện một số mẫu tự động tạo ra:

#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 

Trình biên dịch tạo ra trông như thế này:

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

Ngoài ra, xin lưu ý rằng với phương pháp này, với 4 biến, mỗi biến trên 13 giá trị, Bạn sẽ làm cho trình biên dịch khởi tạo 28561 bản sao của chức năng này. Nếu n của bạn là 50, và bạn vẫn có 4 tùy chọn, bạn sẽ có 6250000 chức năng khởi tạo. Điều này có thể làm cho một biên dịch SLOW.

+0

Cảm ơn, câu trả lời của bạn thực sự hữu ích! – Danvil

+5

'Điều này có thể làm cho một biên dịch SLOW' - Chưa kể một kích thước trước khi xử lý tiếp cận nửa gigabyte và kích thước thực thi lớn nếu bạn tìm thấy một trình biên dịch có thể đối phó với điều đó. –

+0

@Joe: Tuyệt đối. Tôi đã sử dụng điều này cho một bộ bools mà tôi muốn thử templating ra. Tôi nghĩ rằng tôi đã làm được nhiều nhất là <100 tạo ra instantiations. –

8

Những điều không rõ ràng, các mẫu được khởi tạo tại thời gian biên dịch.
Vào thời điểm bạn có tệp thi hành, bạn chỉ có các lớp (instantiations cụ thể của các mẫu đó), không có mẫu nào nữa.

Nếu bạn không biết giá trị tại thời gian biên dịch, bạn không thể có mẫu cho những mục đó.

+0

Điều này không đúng. Đối với các tham số nguyên với một tên miền nhỏ, người ta có thể sử dụng các câu lệnh switch/if, như được chỉ ra trong bài viết của sharth. – Danvil

+0

Không có cách nào 'đạt được việc xây dựng lớp mẫu khi các tham số mẫu chỉ được biết ở thời gian chạy' mà không xây dựng tất cả các trường hợp có thể và thực hiện chuyển đổi thời gian chạy. –

+0

Và câu hỏi là làm thế nào để ví dụ làm điều này chuyển đổi đệ quy (mà không cần viết nó ra trong mã bằng tay). – Danvil

0

Bạn không thể. mẫu chỉ là thời gian biên dịch.

Bạn có thể xây dựng tại thời gian biên dịch tất cả các giá trị mẫu có thể bạn muốn và chọn một trong số chúng trong thời gian chạy.

2

Về mặt kỹ thuật * có thể ** - nhưng không thực tế và gần như chắc chắn là cách sai lầm để tiếp cận vấn đề.

Có một số lý do tại sao P1, P2 và P3 không thể là các biến số nguyên không?


* Bạn có thể nhúng một C++ biên dịch và một bản sao của mã nguồn của bạn, sau đó biên dịch một thư viện động hoặc đối tượng chia sẻ mà thực hiện chức năng máy của bạn cho một tập hợp của P1, P2, P3 - nhưng bạn có thực sự muốn Để làm việc đó? IMO, đó là một điều hoàn toàn điên rồ để làm.

10

Nếu macro là không phải là điều bạn thì bạn cũng có thể tạo ra if-then-else bằng cách sử dụng mẫu:

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

Kể từ khi các cuộc thử nghiệm được lồng nó nên hiệu quả hơn so với giải pháp vĩ mô sharth của.

Bạn có thể mở rộng nó đến nhiều thông số hơn bằng cách thêm các trường hợp sâu hơn.

+0

Điều này là khó khăn: D – Danvil

+0

TMP thường là ... –

1

cách quá muộn, tôi biết, nhưng những gì về vấn đề này:

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

Tôi không biết nếu điều này được áp dụng cho vấn đề hiện tại của bạn, nhưng có thể thấy rằng C++ 11 constexpr có thể những gì bạn đang tìm kiếm - constexpr chức năng có thể được gọi trong thời gian chạy và đồng thời có thể được thực hiện tại thời gian biên dịch.

Việc sử dụng constexpr cũng có lợi ích bổ sung khi tìm kiếm "sạch hơn" so với sử dụng TMP, làm việc với bất kỳ giá trị thời gian chạy nào (không chỉ là giá trị tích phân) trong khi giữ lại hầu hết các lợi ích của TMP như ghi nhớ và thực thi biên dịch. điều này phần nào được trao cho quyết định của trình biên dịch. Trên thực tế, constexpr thường nhanh hơn nhiều so với phiên bản tương đương TMP. Lưu ý rằng nhìn chung, việc sử dụng các mẫu trong thời gian chạy sẽ làm suy yếu một trong các tính năng lớn nhất của mẫu - Thực tế là chúng được xử lý trong thời gian biên dịch và biến mất khá nhiều trong thời gian chạy.

+1

Ví dụ xin vui lòng? – Andrew

Các vấn đề liên quan