2014-10-22 16 views
62

Tôi đã xem phần thứ hai của Walter Brown's CppCon2014 talk on template metaprogramming, trong đó ông đã thảo luận về việc sử dụng tiểu thuyết của mình là xây dựng void_t<>. Trong bài thuyết trình của ông, Peter Sommerlad đã hỏi ông một câu hỏi mà tôi không hiểu lắm. (Liên kết đi trực tiếp vào câu hỏi, mã đang được thảo luận đã diễn ra trực tiếp trước đó)void_t "có thể triển khai khái niệm" không?

Sommerlad hỏi

Walter, điều đó có nghĩa chúng tôi thực sự có thể thực hiện các khái niệm Lite ngay bây giờ?

mà Walter đáp

Oh yeah! Tôi đã thực hiện nó ... Nó không có cú pháp tương tự.

Tôi hiểu sự trao đổi này là về khái niệm Lite. Mẫu này có thực sự là rằng linh hoạt không? Vì lý do gì đó, tôi không thấy nó. Ai đó có thể giải thích (hoặc phác họa) một cái gì đó như thế này có thể nhìn? Đây có phải chỉ là về enable_if và xác định các đặc điểm hay người hỏi đang đề cập đến điều gì?

Mẫu void_t được định nghĩa như sau:

template<class ...> using void_t = void; 

Ông sử dụng này sau đó để phát hiện nếu phát biểu kiểu này được hình thành tốt, sử dụng này để thực hiện các is_copy_assignable loại đặc điểm:

//helper type 
template<class T> 
using copy_assignment_t 
= decltype(declval<T&>() = declval<T const&>()); 

//base case template 
template<class T, class=void> 
struct is_copy_assignable : std::false_type {}; 

//SFINAE version only for types where copy_assignment_t<T> is well-formed. 
template<class T> 
struct is_copy_assignable<T, void_t<copy_assignment_t<T>>> 
: std::is_same<copy_assignment_t<T>,T&> {}; 

Do cuộc nói chuyện, tôi hiểu ví dụ này hoạt động như thế nào, nhưng tôi không thấy làm thế nào chúng ta có được từ đây đến một cái gì đó như khái niệm Lite.

+2

Câu hỏi thú vị, thật không may là nó không thực sự khác với câu hỏi "hãy xem mã nguồn của tôi, được lưu trữ trên (một số kho lưu trữ)". Chỉ tệ hơn, bởi vì nó là một video. Bạn có thể thêm đoạn mã có liên quan vào câu hỏi của mình để câu hỏi đó trở nên độc lập hơn không? Tối thiểu, chỉ định thời gian trong video bạn đang nói, "phần thứ hai" gần như vô dụng. –

+1

@BenVoigt OK, tôi đã cập nhật câu hỏi. Có một mã thời gian trong liên kết youtube bỏ qua trực tiếp câu hỏi. Và tôi đã thêm ví dụ chính mà anh ấy đã sử dụng trong bản trình bày của mình. Tôi hy vọng điều đó làm cho câu hỏi rõ ràng hơn. –

+4

Bạn không thể truy cập từ đó vào khái niệm Lite. Đó là một kỹ thuật tuyệt vời và bạn có thể sử dụng nó cho các mệnh đề "yêu cầu" tốt trong các danh sách tham số mẫu, nhưng bạn không thể làm cho trình biên dịch áp đặt một phần đặt hàng trên các mẫu dựa trên đó "khái niệm" được tinh chỉnh hơn. Bạn cũng không thể áp đặt các yêu cầu đối với các hàm thành viên không phải mẫu của một mẫu lớp, hoặc một số các tính năng ngôn ngữ _entirely new_ khác là một phần của các khái niệm TS. –

Trả lời

105

Có, khái niệm về cơ bản là trang phục cho SFINAE. Thêm vào đó nó cho phép nội tâm sâu sắc hơn cho phép quá tải tốt hơn. Tuy nhiên, nó chỉ hoạt động nếu các vị từ khái niệm được định nghĩa là concept bool. Quá tải được cải tiến không hoạt động với các vị từ khái niệm hiện tại, nhưng có thể sử dụng quá tải có điều kiện. Hãy xem cách chúng ta có thể định nghĩa các biến vị ngữ, hạn chế các mẫu và các hàm quá tải trong C++ 14. Đây là loại dài, nhưng nó đi qua làm thế nào để tạo ra tất cả các công cụ cần thiết để thực hiện điều này trong C + + 14.

Xác định vị từ

Thứ nhất, nó là loại xấu xí để đọc vị với tất cả các std::declvaldecltype ở khắp mọi nơi. Thay vào đó, chúng ta có thể tận dụng lợi thế của một thực tế mà chúng ta có thể hạn chế một hàm sử dụng một decltype dấu (từ bài viết trên blog Eric Niebler của here), như thế này:

struct Incrementable 
{ 
    template<class T> 
    auto requires_(T&& x) -> decltype(++x); 
}; 

Vì vậy, nếu ++x là không hợp lệ, sau đó chức năng requires_ thành viên không thể gọi được.Vì vậy, chúng ta có thể tạo ra một đặc điểm models mà chỉ kiểm tra nếu requires_ là callable sử dụng void_t:

template<class Concept, class Enable=void> 
struct models 
: std::false_type 
{}; 

template<class Concept, class... Ts> 
struct models<Concept(Ts...), void_t< 
    decltype(std::declval<Concept>().requires_(std::declval<Ts>()...)) 
>> 
: std::true_type 
{}; 

kìm hãm Templates

Vì vậy, khi chúng ta muốn hạn chế các mẫu dựa trên khái niệm, chúng tôi sẽ vẫn cần phải sử dụng enable_if , nhưng chúng ta có thể sử dụng macro này để giúp làm cho nó sạch hơn:

#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0 

Vì vậy, chúng ta có thể xác định một chức năng increment đó là hạn chế dựa trên Incrementable khái niệm:

template<class T, REQUIRES(models<Incrementable(T)>())> 
void increment(T& x) 
{ 
    ++x; 
} 

Vì vậy, nếu chúng ta gọi là increment với một cái gì đó không phải là Incrementable, chúng tôi sẽ nhận được một lỗi như thế này:

test.cpp:23:5: error: no matching function for call to 'incrementable' 
    incrementable(f); 
    ^~~~~~~~~~~~~ 
test.cpp:11:19: note: candidate template ignored: disabled by 'enable_if' [with T = foo] 
template<class T, REQUIRES(models<Incrementable(T)>())> 
       ^

Chức năng quá tải

Bây giờ nếu chúng ta muốn làm quá tải , chúng tôi muốn sử dụng quá tải có điều kiện. Giả sử chúng ta muốn tạo một std::advance sử dụng các vị từ khái niệm, chúng ta có thể định nghĩa nó như thế này (cho bây giờ chúng tôi sẽ bỏ qua các trường hợp decrementable):

struct Incrementable 
{ 
    template<class T> 
    auto requires_(T&& x) -> decltype(++x); 
}; 

struct Advanceable 
{ 
    template<class T, class I> 
    auto requires_(T&& x, I&& i) -> decltype(x += i); 
}; 

template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())> 
void advance(Iterator& it, int n) 
{ 
    it += n; 
} 

template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())> 
void advance(Iterator& it, int n) 
{ 
    while (n--) ++it; 
} 

Tuy nhiên, điều này gây ra một tình trạng quá tải nhập nhằng (Trong khái niệm Lite này vẫn sẽ là một quá tải không rõ ràng, trừ khi chúng ta thay đổi các biến vị ngữ của chúng ta để chỉ các vị từ khác trong một concept bool) khi nó được sử dụng với trình lặp vòng lặp std::vector. Những gì chúng tôi muốn làm là đặt hàng các cuộc gọi, mà chúng tôi có thể làm bằng cách sử dụng quá tải có điều kiện. Nó có thể được nghĩ đến việc viết một cái gì đó như thế này (mà không phải là hợp lệ C + +):

template<class Iterator> 
void advance(Iterator& it, int n) if (models<Advanceable(Iterator, int)>()) 
{ 
    it += n; 
} 
else if (models<Incrementable(Iterator)>()) 
{ 
    while (n--) ++it; 
} 

Vì vậy, nếu chức năng đầu tiên không được gọi, nó sẽ gọi chức năng tiếp theo. Vì vậy, cho phép bắt đầu bằng cách thực hiện nó cho hai chức năng. Chúng tôi sẽ tạo ra một lớp được gọi là basic_conditional mà chấp nhận hai đối tượng chức năng như các thông số mẫu:

struct Callable 
{ 
    template<class F, class... Ts> 
    auto requires_(F&& f, Ts&&... xs) -> decltype(
     f(std::forward<Ts>(xs)...) 
    ); 
}; 

template<class F1, class F2> 
struct basic_conditional 
{ 
    // We don't need to use a requires clause here because the trailing 
    // `decltype` will constrain the template for us. 
    template<class... Ts> 
    auto operator()(Ts&&... xs) -> decltype(F1()(std::forward<Ts>(xs)...)) 
    { 
     return F1()(std::forward<Ts>(xs)...); 
    } 
    // Here we add a requires clause to make this function callable only if 
    // `F1` is not callable. 
    template<class... Ts, REQUIRES(!models<Callable(F1, Ts&&...)>())> 
    auto operator()(Ts&&... xs) -> decltype(F2()(std::forward<Ts>(xs)...)) 
    { 
     return F2()(std::forward<Ts>(xs)...); 
    } 
}; 

Vì vậy, bây giờ mà có nghĩa là chúng ta cần phải xác định chức năng của chúng tôi như các chức năng các đối tượng thay vì:

struct advance_advanceable 
{ 
    template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())> 
    void operator()(Iterator& it, int n) const 
    { 
     it += n; 
    } 
}; 

struct advance_incrementable 
{ 
    template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())> 
    void operator()(Iterator& it, int n) const 
    { 
     while (n--) ++it; 
    } 
}; 

static conditional<advance_advanceable, advance_incrementable> advance = {}; 

Vì vậy, bây giờ nếu chúng ta cố gắng sử dụng nó với một std::vector:

std::vector<int> v = { 1, 2, 3, 4, 5, 6 }; 
auto iterator = v.begin(); 
advance(iterator, 4); 
std::cout << *iterator << std::endl; 

nó sẽ biên dịch và in ra 5.

Tuy nhiên, std::advance thực sự có ba quá tải, vì vậy chúng tôi có thể sử dụng basic_conditional để thực hiện conditional mà làm việc cho bất kỳ số lượng các chức năng sử dụng đệ quy:

template<class F, class... Fs> 
struct conditional : basic_conditional<F, conditional<Fs...>> 
{}; 

template<class F> 
struct conditional<F> : F 
{}; 

Vì vậy, bây giờ chúng ta có thể viết đầy đủ std::advance như thế này:

struct Incrementable 
{ 
    template<class T> 
    auto requires_(T&& x) -> decltype(++x); 
}; 

struct Decrementable 
{ 
    template<class T> 
    auto requires_(T&& x) -> decltype(--x); 
}; 

struct Advanceable 
{ 
    template<class T, class I> 
    auto requires_(T&& x, I&& i) -> decltype(x += i); 
}; 

struct advance_advanceable 
{ 
    template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())> 
    void operator()(Iterator& it, int n) const 
    { 
     it += n; 
    } 
}; 

struct advance_decrementable 
{ 
    template<class Iterator, REQUIRES(models<Decrementable(Iterator)>())> 
    void operator()(Iterator& it, int n) const 
    { 
     if (n > 0) while (n--) ++it; 
     else 
     { 
      n *= -1; 
      while (n--) --it; 
     } 
    } 
}; 

struct advance_incrementable 
{ 
    template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())> 
    void operator()(Iterator& it, int n) const 
    { 
     while (n--) ++it; 
    } 
}; 

static conditional<advance_advanceable, advance_decrementable, advance_incrementable> advance = {}; 

quá tải Với Lambdas

Tuy nhiên, additio nally, chúng tôi có thể sử dụng lambdas để viết nó thay vì các đối tượng chức năng có thể giúp làm cho nó sạch hơn để viết. Vì vậy, chúng tôi sử dụng STATIC_LAMBDA này vĩ mô để xây dựng lambdas tại thời gian biên dịch:

struct wrapper_factor 
{ 
    template<class F> 
    constexpr wrapper<F> operator += (F*) 
    { 
     return {}; 
    } 
}; 

struct addr_add 
{ 
    template<class T> 
    friend typename std::remove_reference<T>::type *operator+(addr_add, T &&t) 
    { 
     return &t; 
    } 
}; 

#define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + [] 

Và thêm một chức năng make_conditional đó là constexpr:

template<class... Fs> 
constexpr conditional<Fs...> make_conditional(Fs...) 
{ 
    return {}; 
} 

Sau đó, chúng tôi bây giờ có thể viết advance chức năng như thế này:

constexpr const advance = make_conditional(
    STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Advanceable(decltype(it), int)>())) 
    { 
     it += n; 
    }, 
    STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Decrementable(decltype(it))>())) 
    { 
     if (n > 0) while (n--) ++it; 
     else 
     { 
      n *= -1; 
      while (n--) --it; 
     } 
    }, 
    STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Incrementable(decltype(it))>())) 
    { 
     while (n--) ++it; 
    } 
); 

Nhỏ hơn và dễ đọc hơn so với sử dụng phiên bản đối tượng hàm.

Bên cạnh đó, chúng ta có thể định nghĩa một hàm modeled giảm xuống decltype xấu xa:

template<class Concept, class... Ts> 
constexpr auto modeled(Ts&&...) 
{ 
    return models<Concept(Ts...)>(); 
} 

constexpr const advance = make_conditional(
    STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Advanceable>(it, n))) 
    { 
     it += n; 
    }, 
    STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Decrementable>(it))) 
    { 
     if (n > 0) while (n--) ++it; 
     else 
     { 
      n *= -1; 
      while (n--) --it; 
     } 
    }, 
    STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Incrementable>(it))) 
    { 
     while (n--) ++it; 
    } 
); 

Cuối cùng, nếu bạn quan tâm trong việc sử dụng các giải pháp thư viện hiện có (chứ không phải là cán của riêng bạn như tôi đã được hiển thị). Có thư viện Tick cung cấp một khuôn khổ để xác định các khái niệm và ràng buộc các khuôn mẫu. Và thư viện Fit có thể xử lý các chức năng và quá tải.

+0

Bravo! Bài đăng tuyệt vời. Thực sự hoàn thành. –

+2

Cảm ơn bạn đã dành thời gian viết một câu trả lời rộng lớn như vậy. Đây là nhiều hơn tôi mong đợi, nhưng chính xác là loại câu trả lời tôi đã hy vọng. –

+1

upvoted do chất lượng cao và số lượng ... nhưng OMG buồn nôn C++ 11 của tôi là đá. –

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