2009-06-11 72 views
15

Với tham chiếu đến this question, ai cũng có thể giải thích và đăng mã ví dụ về lập trình meta? Tôi googled thuật ngữ lên, nhưng tôi thấy không có ví dụ để thuyết phục tôi rằng nó có thể được sử dụng bất kỳ thực tế.Lập trình meta là gì?

Cùng một lưu ý, là Qt's Meta Object System một hình thức lập trình meta?

jrh

+2

Tôi không phải là lập trình viên QT, nhưng nó trông giống như một hình thức lập trình meta. Tuy nhiên, khi lập trình viên C++ nói về metaprogramming, họ thường đề cập đến lập trình meta template cụ thể (không liên quan gì đến hệ thống meta-object của QT). Một số ngôn ngữ khác tồn tại: Một số ngôn ngữ cho phép bạn lập trình meta tại thời gian chạy (thao tác hoặc mở rộng các loại trong khi chương trình đang chạy), và cách tiếp cận của QT dường như tạo ra các tệp mã mới để đượC#included. Nhưng đó là tất cả metaprogramming, trong đó một metaprogram chỉ đơn giản là một chương trình mà tạo ra, hoặc thao tác một chương trình. – jalf

+0

Bạn có quan tâm đến lập trình meta tổng quát hay chỉ là lập trình meta mẫu C++? –

Trả lời

24

Hầu hết các ví dụ cho đến nay đã hoạt động trên các giá trị (số máy tính của pi, giai thừa của N hoặc tương tự), và đó là khá nhiều sách giáo khoa, nhưng chúng thường không hữu ích. Thật khó để tưởng tượng một tình huống mà bạn thực sự cần trình biên dịch để tính toán chữ số thứ 17 của pi. Hoặc bạn tự mã hóa nó, hoặc tính toán nó khi chạy.

Một ví dụ mà có thể phù hợp hơn với thế giới thực có thể là thế này:

Hãy nói rằng chúng ta có một lớp mảng mà kích thước là một tham số mẫu (vì vậy đây sẽ khai báo một mảng của 10 số nguyên: array<int, 10>)

Bây giờ chúng tôi có thể muốn nối hai mảng và chúng tôi có thể sử dụng một chút lập trình meta để tính toán kích thước mảng kết quả.

template <typename T, int lhs_size, int rhs_size> 
array<T, lhs_size + rhs_size> concat(const array<T, lhs_size>& lhs, const array<T, rhs_size>& rhs){ 

    array<T, lhs_size + rhs_size> result; 
    // copy values from lhs and rhs to result 
    return result; 

} 

Một ví dụ rất đơn giản, nhưng ít nhất các loại có liên quan thực tế. Hàm này tạo ra một mảng có kích thước chính xác, nó làm như vậy tại thời gian biên dịch, và với toàn bộ kiểu an toàn. Và đó là tính toán thứ gì đó mà chúng tôi không thể thực hiện bằng cách mã hóa các giá trị (chúng tôi có thể muốn ghép nhiều mảng với kích thước khác nhau) hoặc khi chạy (vì sau đó chúng tôi sẽ mất thông tin loại)

Phổ biến hơn, mặc dù, bạn có xu hướng sử dụng metaprogramming cho các loại, chứ không phải là giá trị.

Một ví dụ điển hình có thể được tìm thấy trong thư viện chuẩn. Mỗi loại vùng chứa định nghĩa loại trình lặp riêng của nó, nhưng các con trỏ cũ đơn giản có thể cũng là được sử dụng làm trình lặp. Về mặt kỹ thuật, một trình vòng lặp được yêu cầu để hiển thị một số thành viên typedef, chẳng hạn như value_type và các con trỏ rõ ràng là không làm điều đó. Vì vậy, chúng tôi sử dụng một chút lập trình meta để nói "oh, nhưng nếu loại trình biến đổi hóa ra là một con trỏ, thì thay vào đó, định dạng của nó sẽ sử dụng định nghĩa này."

Có hai điều cần lưu ý về điều này. Đầu tiên là chúng ta đang thao tác các loại, không phải giá trị Chúng ta không nói "giai thừa của N là như vậy và như vậy", nhưng đúng hơn, "value_type của một loại T được định nghĩa là ..."

điều thứ hai là nó được sử dụng để tạo điều kiện lập trình chung. (Iterator sẽ không phải là một khái niệm rất chung chung nếu nó không hoạt động cho các ví dụ đơn giản nhất, một con trỏ vào một mảng. Vì vậy, chúng tôi sử dụng một chút lập trình meta để điền vào các chi tiết cần thiết cho một con trỏ được coi là hợp lệ trình lặp).

Đây là trường hợp sử dụng khá phổ biến cho lập trình meta. Chắc chắn, bạn có thể sử dụng nó cho một loạt các mục đích khác (mẫu Expression là một ví dụ thường được sử dụng, nhằm tối ưu hóa các tính toán đắt tiền, và Boost.Spirit là một ví dụ về việc hoàn toàn overboard và cho phép bạn định nghĩa trình phân tích cú pháp của riêng bạn tại compile- thời gian), nhưng có lẽ cách sử dụng phổ biến nhất là làm mịn những va chạm nhỏ và các trường hợp góc mà nếu không sẽ yêu cầu xử lý đặc biệt và làm cho chương trình chung không thể.

+1

... và cuối cùng là một ví dụ thế giới thực - như được yêu cầu cho :) – xtofl

+1

+1 Tôi đã hy vọng một người nào đó đã đăng một ví dụ thế giới thực. – Tom

+0

Tôi thực sự sử dụng lập trình meta mẫu (TMP) cho một loại tính toán giá trị mà tôi sử dụng khá thường xuyên. Tôi có một TMP đơn giản tính toán lũy thừa tiếp theo lớn hơn hoặc bằng một hằng số. Tôi sử dụng nó cho lựa chọn bin nguồn-of-2 dựa trên kích thước cấu trúc khi chạy. – Adisak

5

Dưới đây là một ví dụ phổ biến:

template <int N> 
    struct fact { 
     enum { value = N * fact<N-1>::value }; 
    }; 

    template <> 
    struct fact<1> { 
     enum { value = 1 }; 
    }; 

    std::cout << "5! = " << fact<5>::value << std::endl; 

Bạn đang cơ bản sử dụng các mẫu để tính toán một thừa.

Một ví dụ thực tế hơn tôi thấy gần đây là một mô hình đối tượng dựa trên các bảng DB sử dụng các lớp mẫu để mô hình các mối quan hệ khóa ngoại trong các bảng bên dưới.

+3

Lợi thế ở đây là giai thừa 5 được tính toán tại thời gian biên dịch. Vấn đề với điều này là nếu bạn thực hiện << fact , nó cũng được tính toán tại thời gian biên dịch và sẽ dẫn đến thời gian xây dựng dài hơn. Tuy nhiên đó là một thủ thuật gọn gàng – Glen

+0

Không thể thực hiện được rằng sẽ được tính toán thời gian biên dịch. AFAIK, các trình biên dịch sử dụng để có một giới hạn cấu hình của việc khởi tạo lớp mẫu trong trường hợp các trường hợp đệ quy như vậy ... –

+2

Nhưng điểm POINT của ví dụ của Glen vẫn hợp lệ; càng có nhiều công việc đang được thực hiện với lập trình mẫu meta thì thời gian xây dựng càng dài. –

2

Ví dụ sau được lấy từ cuốn sách xuất sắc C++ Templates - The complete guide.

#include <iostream> 
using namespace std; 

template <int N> struct Pow3 { 
    enum { pow = 3 * Pow3<N-1>::pow }; 
} 

template <> struct Pow3<0> { 
    enum { pow = 1 }; 
} 

int main() { 
    cout << "3 to the 7 is " << Pow<7>::pow << "\n"; 
} 

Điểm của mã này là phép tính đệ quy của lũy thừa thứ 7 diễn ra trong thời gian biên dịch chứ không phải thời gian chạy. Do đó, nó rất hiệu quả về hiệu suất thời gian chạy, với chi phí biên dịch chậm hơn.

Điều này có hữu ích không? Trong ví dụ này, có lẽ không. Nhưng có những vấn đề khi thực hiện các phép tính trong thời gian biên dịch có thể là một lợi thế.

+0

Không nên là Pow <7>? – Blindy

+0

Urgh - vâng, nên - cảm ơn. –

+2

Không nên là Pow3 <7>? –

7

Khái niệm này xuất phát hoàn toàn từ tên Meta- có nghĩa là trừu tượng từ thứ mà nó được đặt trước.
Trong 'phong cách đàm thoại' hơn để làm điều gì đó với điều đó chứ không phải là chính điều đó.

Về vấn đề này, lập trình meta cơ bản là viết mã, viết mã (hoặc nguyên nhân được viết) nhiều mã hơn.

Hệ thống mẫu C++ là chương trình meta vì nó không đơn giản thay thế văn bản (như bộ tiền xử lý c) nhưng có phương tiện tương tác (phức tạp và không hiệu quả) tương tác với cấu trúc mã mà nó phân tích thành mã đầu ra. phức tạp hơn. Về vấn đề này, tiền xử lý mẫu trong C++ là Turing hoàn thành. Đây không phải là yêu cầu để nói rằng một điều gì đó là lập trình meta nhưng gần như chắc chắn là đủ để được tính như vậy.

Các công cụ tạo mã được parametrizable có thể được coi là lập trình meta nếu logic mẫu của chúng đủ phức tạp.

Càng gần một hệ thống sẽ làm việc với cây cú pháp trừu tượng đại diện cho ngôn ngữ (trái ngược với dạng văn bản mà chúng ta đại diện), càng có nhiều khả năng nó được coi là lập trình meta.

Từ khi nhìn vào mã QT MetaObjects tôi sẽ không (từ kiểm tra cursory) gọi nó là lập trình meta theo nghĩa thường dành cho những thứ như hệ thống mẫu C++ hoặc Lisp macro.Nó dường như đơn giản là một dạng tạo mã, nó đưa một số chức năng vào các lớp hiện tại ở giai đoạn biên dịch (nó có thể được xem như là tiền thân của kiểu lập trình hướng đối tượng Aspect hiện đang thịnh hành hoặc các hệ thống đối tượng dựa trên nguyên mẫu trong các ngôn ngữ như JavaScripts

Như ví dụ về các loại chiều dài rất khó, bạn có thể thực hiện việc này trong C++ có Boost MPLtutorial cho bạn thấy làm thế nào để nhận được:

Dimensioned types (Đơn vị đo)

quantity<float,length> l(1.0f); 
quantity<float,mass> m(2.0f); 
m = l; // compile-time type error 

Higher Order Metafunctions

hai lần (f, x): = f (f (x))

template <class F, class X> 
struct twice 
    : apply1<F, typename apply1<F,X>::type> 
{}; 

struct add_pointer_f 
{ 
    template <class T> 
    struct apply : boost::add_pointer<T> {}; 
}; 

Bây giờ chúng ta có thể sử dụng hai lần với add_pointer_f để xây dựng con trỏ-to-con trỏ:

BOOST_STATIC_ASSERT((
    boost::is_same< 
     twice<add_pointer_f, int>::type 
     , int** 
    >::value 
)); 
4

Ví dụ khác: trong trường hợp này, tecnique siêu lập trình được sử dụng để nhận giá trị PI chính xác tùy ý khi biên dịch bằng thuật toán Gauss-Legendre.

Tại sao tôi nên sử dụng một cái gì đó như thế trong thế giới thực? Ví dụ để tránh lặp lại tính toán, để có được thực thi nhỏ hơn, để điều chỉnh mã để tối đa hóa hiệu suất trên một kiến ​​trúc cụ thể, ...

Cá nhân tôi yêu siêu lập trình vì tôi ghét lặp lại công cụ và vì tôi có thể điều chỉnh các hằng số khai thác giới hạn kiến ​​trúc .

Tôi hy vọng bạn thích điều đó.

Chỉ 2 xu của tôi.

/** 
* FILE  : MetaPI.cpp 
* COMPILE : g++ -Wall -Winline -pedantic -O1 MetaPI.cpp -o MetaPI 
* CHECK : g++ -Wall -Winline -pedantic -O1 -S -c MetaPI.cpp [read file MetaPI.s] 
* PURPOSE : simple example template metaprogramming to compute the 
*    value of PI using [1,2]. 
* 
* TESTED ON: 
* - Windows XP, x86 32-bit, G++ 4.3.3 
* 
* REFERENCES: 
* [1]: http://en.wikipedia.org/wiki/Gauss%E2%80%93Legendre_algorithm 
* [2]: http://www.geocities.com/hjsmithh/Pi/Gauss_L.html 
* [3]: http://ubiety.uwaterloo.ca/~tveldhui/papers/Template-Metaprograms/meta-art.html 
* 
* NOTE: to make assembly code more human-readable, we'll avoid using 
*  C++ standard includes/libraries. Instead we'll use C's ones. 
*/ 

#include <cmath> 
#include <cstdio> 

template <int maxIterations> 
inline static double compute(double &a, double &b, double &t, double &p) 
{ 
    double y = a; 
    a = (a + b)/2; 
    b = sqrt(b * y); 
    t = t - p * ((y - a) * (y - a)); 
    p = 2 * p; 

    return compute<maxIterations - 1>(a, b, t, p); 
} 

// template specialization: used to stop the template instantiation 
// recursion and to return the final value (pi) computed by Gauss-Legendre algorithm 
template <> 
inline double compute<0>(double &a, double &b, double &t, double &p) 
{ 
    return ((a + b) * (a + b))/(4 * t); 
} 

template <int maxIterations> 
inline static double compute() 
{ 
    double a = 1; 
    double b = (double)1/sqrt(2.0); 
    double t = (double)1/4; 
    double p = 1; 

    return compute<maxIterations>(a, b, t, p); // call the overloaded function 
} 

int main(int argc, char **argv) 
{ 
    printf("\nTEMPLATE METAPROGRAMMING EXAMPLE:\n"); 
    printf("Compile-time PI computation based on\n"); 
    printf("Gauss-Legendre algorithm (C++)\n\n"); 

    printf("Pi=%.16f\n\n", compute<5>()); 

    return 0; 
} 
+0

Hàm sqrt() sẽ không chạy trong thời gian biên dịch vì vậy đây không phải là tính toán giá trị của PI trong thời gian biên dịch, đúng không? Nó chỉ đặt ra một hàm đệ quy gọi chiều sâu trong thời gian biên dịch. –

8

Mặc dù nó lớn (2000loc) Tôi đã thực hiện một hệ thống lớp phản trong C++ là trình biên dịch độc lập và bao gồm marshalling đối tượng và siêu dữ liệu nhưng không có chi phí lưu trữ hoặc truy cập hình phạt thời gian. Đó là chương trình siêu lập trình lõi cứng, và được sử dụng trong một trò chơi trực tuyến rất lớn để lập bản đồ các đối tượng trò chơi cho truyền dẫn mạng và ánh xạ cơ sở dữ liệu (ORM).

Dù sao thì phải mất một lúc để biên dịch, khoảng 5 phút, nhưng có lợi ích là nhanh như mã điều chỉnh tay cho từng đối tượng. Vì vậy, nó tiết kiệm rất nhiều tiền bằng cách giảm đáng kể thời gian CPU trên máy chủ của chúng tôi (CPU sử dụng là 5% của những gì nó được sử dụng để được).

+1

Sẽ xuất bản một bài viết trên hệ thống trong tương lai gần, sau khi tôi được thông báo với luật sư của chúng tôi. –

+0

Âm thanh thú vị! : D Tôi mong chờ bài viết. –

+1

Mọi cập nhật về cái này? Tôi rất muốn xem mã, vì siêu dữ liệu không phải trên đầu luôn thú vị. – Xeo

2

Thật khó để nói những gì lập trình meta C++ . Tôi càng cảm thấy nó giống như giới thiệu 'các loại' như là các biến, theo cách lập trình hàm có nó. Nó làm cho lập trình khai báo có thể trong C++.

Đó là cách dễ dàng hơn để hiển thị các ví dụ.

Một trong những mục yêu thích của tôi là một 'lừa' (hoặc mẫu :)) để flatte nhân lồng switch/case khối:

#include <iostream> 
using namespace std; 

enum CCountry { Belgium, Japan }; 
enum CEra  { ancient, medieval, future }; 

// nested switch 
void historic(CCountry country, CEra era) { 
    switch(country) { 
     case(Belgium): 
      switch(era) { 
      case(ancient): cout << "Ambiorix"; break; 
      case(medieval): cout << "Keizer Karel"; break; 
      } 
      break; 
     case(Japan): 
      switch(era) { 
      case(future): cout << "another Ruby?"; break; 
      case(medieval): cout << "Musashi Mijamoto"; break; 
      } 
      break; 
    } 
} 


// the flattened, metaprogramming way 
// define the conversion from 'runtime arguments' to compile-time arguments (if needed...) 
// or use just as is. 
template< CCountry country, CEra era > void thistoric(); 


template<> void thistoric<Belgium, ancient>() { cout << "Ambiorix"; } 
template<> void thistoric<Belgium, medieval>() { cout << "Keizer Karel"; } 
template<> void thistoric<Belgium, future >() { cout << "Beer, lots of it"; } 

template<> void thistoric<Japan, ancient>() { cout << "wikipedia"; } 
template<> void thistoric<Japan, medieval>() { cout << "Musashi"; } 
template<> void thistoric<Japan, future >() { cout << "another Ruby?"; } 


// optional: conversion from runtime to compile-time 
// 
template< CCountry country > struct SelectCountry { 
    static void select(CEra era) { 
    switch (era) { 
      case(medieval): thistoric<country, medieval>(); break; 
      case(ancient ): thistoric<country, ancient >(); break; 
      case(future ): thistoric<country, future >(); break; 

    } 
    } 
}; 

void Thistoric (CCountry country, CEra era) { 
    switch(country) { 
      case(Belgium): SelectCountry<Belgium>::select(era); break; 
      case(Japan ): SelectCountry<Japan >::select(era); break; 
    } 
    } 



int main() { 
    historic(Belgium, medieval); // plain, nested switch 
    thistoric<Belgium,medieval>(); // direct compile time switch 
    Thistoric(Belgium, medieval);// flattened nested switch 
    return 0; 
} 
0

QtMetaObject về cơ bản thực hiện phản xạ (Reflection) và một trong những hình thức chủ yếu của metaprogramming, khá mạnh mẽ thực sự. Nó tương tự như sự phản chiếu của Java và nó cũng thường được sử dụng trong các ngôn ngữ động (Python, Ruby, PHP ...).Nó dễ đọc hơn các mẫu, nhưng cả hai đều có ưu và khuyết điểm của chúng.

2

Lần duy nhất tôi cần sử dụng Boost.MPL trong công việc ban ngày là khi tôi cần chuyển đổi boost::variant đến và đi từ QVariant.

boost::variant có cơ chế truy cập O (1), hướng boost::variant đến QVariant gần như không đáng kể.

Tuy nhiên, QVariant không có một cơ chế thăm viếng, vì vậy để chuyển đổi nó thành một boost::variant, bạn cần phải lặp qua các mpl::list các loại mà các cụ boost::variant instantiation có thể giữ, và đối với từng loại yêu cầu QVariant liệu nó chứa loại đó và nếu có, hãy trích xuất giá trị và trả lại giá trị đó theo số boost::variant. Nó khá thú vị, bạn nên thử nó :)

0

Đây là một "tính toán giá trị" đơn giản dọc theo các dòng của giai thừa. Tuy nhiên, đó là một trong những bạn có nhiều khả năng thực sự sử dụng trong mã của bạn.

Macro CT_NEXTPOWEROFTWO2 (VAL) sử dụng lập trình meta mẫu để tính lũy thừa tiếp theo lớn hơn hoặc bằng giá trị cho các giá trị được biết tại thời gian biên dịch.

template<long long int POW2VAL> class NextPow2Helper 
{ 
    enum { c_ValueMinusOneBit  = (POW2VAL&(POW2VAL-1)) }; 
public: 
    enum { 
     c_TopBit      = (c_ValueMinusOneBit) ? 
      NextPow2Helper<c_ValueMinusOneBit>::c_TopBit : POW2VAL, 
     c_Pow2ThatIsGreaterOrEqual = (c_ValueMinusOneBit) ? 
      (c_TopBit<<1) : c_TopBit 
    }; 
}; 
template<> class NextPow2Helper<1> 
{ public: enum { c_TopBit = 1, c_Pow2ThatIsGreaterOrEqual = 1 }; }; 
template<> class NextPow2Helper<0> 
{ public: enum { c_TopBit = 0, c_Pow2ThatIsGreaterOrEqual = 0 }; }; 
// This only works for values known at Compile Time (CT) 
#define CT_NEXTPOWEROFTWO2(VAL) NextPow2Helper<VAL>::c_Pow2ThatIsGreaterOrEqual 
Các vấn đề liên quan