7

Tôi đã xác định loại hoạt động như một số nguyên. Tôi muốn xác định một chuyên môn cho std :: common_type cho loại của tôi. Tuy nhiên, chuyên môn này sẽ có thể cung cấp cho common_type của bounded_integer (lớp của tôi) kết hợp với bất kỳ số đối số nào khác là các loại số nguyên bounded_integer hoặc tích hợp khác. Tôi muốn mã sau đây có hiệu lực:Chuyên môn từng phần của mẫu lớp cho một loại xuất hiện ở bất kỳ vị trí nào của gói tham số mẫu variadic

std::common_type<bounded_integer<1, 10>>::type 
std::common_type<bounded_integer<1, 10>, int>::type 
std::common_type<int, long, bounded_integer<1, 10>>::type 
std::common_type<int, int, long, short, long long, short, bounded_integer<1, 10>, int, short, short, short, ..., short, bounded_integer<1, 10>>::type 

Lần đầu tiên tôi giải quyết vấn đề này là sử dụng enable_if. Tuy nhiên, tôi nhận ra rằng điều này sẽ không cho phép tôi để phân biệt từ định nghĩa thư viện của common_type, như những gì tôi đã có được chủ yếu

#include <type_traits> 

class C {}; 

template<typename T, typename... Ts> 
class contains_c { 
public: 
     static constexpr bool value = contains_c<T>::value or contains_c<Ts...>::value; 
}; 
template<typename T> 
class contains_c<T> { 
public: 
     static constexpr bool value = std::is_same<T, C>::value; 
}; 

namespace std { 

template<typename... Args, typename std::enable_if<contains_c<Args...>::value>::type> 
class common_type<Args...> { 
public: 
     using type = C; 
}; 

}  // namespace std 

int main() { 
} 

Trường hợp 'đặc tả từng phần' thực sự chỉ là "bất kỳ đối số", nay đã không còn chuyên môn hơn những gì chúng tôi có.

Vì vậy, nó có vẻ như là giải pháp duy nhất là để yêu cầu người dùng của tôi để làm một trong các cách sau:

  1. luôn đặt bounded_integer như là đối số đầu tiên common_type
  2. luôn luôn sử dụng make_bounded của tôi (built-in giá trị số nguyên) để chuyển đổi các số nguyên của chúng thành bounded_integer (vì vậy không có chuyên môn của common_type cho các kiểu dựng sẵn kết hợp với bounded_integer)
  3. không bao giờ đặt bounded_integer ở vị trí lớn hơn N, trong đó N là một số tôi xác định , tương tự như mẫu Phiên bản cũ của Visual Studio làm việc xung quanh

3 sẽ giống như thế này:

// all_bounded_integer_or_integral and all_are_integral defined elsewhere with obvious definitions 
template<intmax_t minimum, intmax_t maximum, typename... Ts, typename = type std::enable_if<all_bounded_integer_or_integral<Ts...>::value>::type> 
class common_type<bounded_integer<minimum, maximum>, Ts...> { 
}; 
template<typename T1, intmax_t minimum, intmax_t maximum, typename... Ts, typename = typename std::enable_if<all_are_integral<T1>::value>::type, typename = typename std::enable_if<all_bounded_integer_or_builtin<Ts...>::value>::type> 
class common_type<T1, bounded_integer<minimum, maximum>, Ts...> { 
}; 
template<typename T1, typename T2, intmax_t minimum, intmax_t maximum, typename... Ts, typename = typename std::enable_if<all_are_integral<T1, T2>::value>::type, typename = typename std::enable_if<all_bounded_integer_or_builtin<Ts...>::value>::type> 
class common_type<T1, T2, bounded_integer<minimum, maximum>, Ts...> { 
}; 
// etc. 

Có cách nào tốt hơn để thực hiện điều này (mẫu chuyên môn khi tất cả các loại đáp ứng một điều kiện và bất kỳ các loại đáp ứng điều kiện khác) cho một lớp rằng tôi không thể thay đổi định nghĩa ban đầu cho?

EDIT:

Dựa trên câu trả lời, tôi không đủ rõ ràng trong vấn đề của mình.

Thứ nhất, hành vi mong đợi:

Nếu ai đó gọi std :: common_type với tất cả các loại là một thể hiện của bounded_integer hoặc một built-in kiểu số, tôi muốn kết quả là một bounded_integer rằng có tối thiểu của tất cả các mức tối thiểu có thể và tối đa tất cả các mức tối đa có thể.

Vấn đề:

Tôi có một giải pháp làm việc khi có người gọi std :: common_type trên bất kỳ số lượng bounded_integer. Tuy nhiên, nếu tôi chỉ chuyên phiên bản hai cãi nhau, sau đó tôi chạy vào vấn đề sau đây:

std::common_type<int, unsigned, bounded_integer<0, std::numeric_limits<unsigned>::max() + 1>

nên cho tôi

bounded_integer<std::numeric_limits<int>::min(), std::numeric_limits<unsigned>::max() + 1>

Tuy nhiên, nó không.Trước tiên, nó áp dụng common_type cho intunsigned, tuân theo quy tắc quảng cáo tích phân tiêu chuẩn, cho số unsigned. Sau đó nó sẽ trả về kết quả của common_type với unsignedbounded_integer của tôi, cho

bounded_integer<0, std::numeric_limits<unsigned>::max() + 1>

Vì vậy, bằng cách thêm unsigned đến giữa các gói thông số, mặc dù nó nên hoàn toàn không có tác động vào loại kết quả (dãy của nó hoàn toàn nằm trong phạm vi của tất cả các loại khác), nó vẫn ảnh hưởng đến kết quả. Cách duy nhất tôi có thể nghĩ ra để ngăn chặn điều này là chuyên biệt std::common_type cho bất kỳ số nào được tích hợp sẵn trong số nguyên theo sau là bounded_integer, theo sau là bất kỳ số nào được tích hợp sẵn hoặc số nguyên bounded_integer.

Câu hỏi của tôi là: làm cách nào tôi có thể thực hiện việc này mà không cần phải xấp xỉ bằng cách viết một số tham số tùy ý theo sau là bounded_integer theo sau là gói tham số hoặc điều này là không thể?

EDIT 2:

Lý do mà common_type sẽ cung cấp cho các giá trị sai có thể được giải thích bằng lý luận này sau khi tiêu chuẩn (trích dẫn từ N3337)

Các common_type của intunsignedunsigned. Ví dụ: http://ideone.com/9IxKIW. Standardese có thể được tìm thấy trong § 20.9.7.6/3, nơi common_type trong hai giá trị là

typedef decltype(true ? declval<T>() : declval<U>()) type;

Trong § 5,16/6, nó nói

Các toán hạng thứ hai và thứ ba có số học hay loại đếm; các chuyển đổi số học thông thường được thực hiện để đưa chúng vào loại phổ biến và kết quả là loại đó.

Quá trình chuyển đổi số học thông thường được định nghĩa trong § 5/9 như

Ngược lại, nếu các toán hạng có kiểu dữ liệu integer unsigned có rank lớn hơn hoặc tương đương với cấp bậc của các loại khác toán hạng, toán hạng có loại số nguyên đã ký được chuyển đổi thành loại toán hạng có loại số nguyên không dấu.

+0

điều gì nên 'std :: common_type , ngắn> :: loại' đầu ra? 'short' hoặc' ranged_integer :: min(), std :: numeric_limits :: max()> '. – brunocodutra

+0

@brunocodutra: Tôi đã cập nhật bài đăng của mình để giải thích. Tôi mong đợi kết quả của 'common_type' của bất kỳ biểu thức nào chứa tất cả' ranged_integer' và các kiểu tách rời là một 'ranged_integer' với một phạm vi chứa tất cả các giá trị có thể có từ bất kỳ kiểu nào. –

Trả lời

3

std::common_type ngoại suy chuyên môn hai đối số của chính nó vào trường hợp n-đối số. Bạn chỉ cần chuyên các trường hợp hai đối số.

template< typename other, int low, int high > 
struct common_type< other, ::my::ranged_integer< low, high > > { 
    using type = other; 
}; 

template< typename other, int low, int high > 
struct common_type< ::my::ranged_integer< low, high >, other > { 
    using type = other; 
}; 

template< int low, int high > 
struct common_type< ::my::ranged_integer< low, high >, 
        ::my::ranged_integer< low, high > > { 
    using type = ::my::ranged_integer< low, high >; 
}; 

Lá này chưa được xác định là common_type giữa các số nguyên khác nhau. Tôi cho rằng bạn có thể làm điều đó với minmax.

Bạn cũng có thể tạo một đặc điểm is_ranged_integer nếu lớp của bạn hỗ trợ kế thừa.

Đừng quên đặt thư viện của bạn bên trong một không gian tên.

+0

+1 Tham chiếu Std: [meta.trans.other]/3. – dyp

+0

@Potatoswatter Tôi tin rằng DyP đang đề cập đến dấu '{type> 'phụ. – brunocodutra

+0

@brunocodutra cảm ơn, thậm chí không nhìn thấy nó: P – Potatoswatter

0

Có lẽ tôi đang thiếu cái gì, nhưng bạn không chỉ muốn một cái gì đó như thế này đối với trường hợp int:

namespace std { 

// first give ranged_integer a ground zero 
    template<intmax_t minimum, intmax_t maximum> 
    class common_type<ranged_integer<minimum, maximum>> { 
     typedef typename ranged_integer<minimum, maximum> type; 
    }; 

// sort out int 
    template<intmax_t minimum, intmax_t maximum> 
    class common_type<int, ranged_integer<minimum, maximum>> { 
     typedef typename ranged_integer<minimum, maximum> type; 
    }; 

    template<intmax_t minimum, intmax_t maximum> 
    class common_type<ranged_integer<minimum, maximum>, int>> { 
     typedef typename ranged_integer<minimum, maximum> type; 
    }; 

lặp lại này cho `long, long long, vv ... và common_type 's định nghĩa mẫu sẽ xử lý tất cả các trường hợp variadic chỉ với một kiểu ranged_integer?

1

Dưới đây là một thực thể: Câu trả lời

#include <limits> 
#include <utility> 
#include <iostream> 

template<typename T, typename U> 
static constexpr auto min(T x, U y) -> decltype(x < y ? x : y) 
{ 
    return x < y ? x : y; 
} 

template<typename T, typename U> 
static constexpr auto max(T x, U y) -> decltype(x < y ? x : y) 
{ 
    return x > y ? x : y; 
} 

template<intmax_t f, intmax_t l> 
struct ranged_integer 
{ 
    static intmax_t const first = f; 
    static intmax_t const last = l; 
    static_assert(l > f, "invalid range"); 
}; 

template <class ...T> struct common_type 
{ 
}; 

template <class T> 
struct common_type<T> 
{ 
    typedef T type; 
}; 

template <class T, class U> 
struct common_type<T, U> 
{ 
    typedef decltype(true ? std::declval<T>() : std::declval<U>()) type; 
}; 

template <class T, intmax_t f, intmax_t l> 
struct common_type<T, ranged_integer<f,l>> 
{ 
    typedef ranged_integer< min(std::numeric_limits<T>::min(),f) , max(std::numeric_limits<T>::max(),l) > type; 
}; 

template <class T, intmax_t f, intmax_t l> 
struct common_type<ranged_integer<f,l>, T> 
{ 
    typedef typename common_type<T, ranged_integer<f,l>>::type type; 
}; 

template <intmax_t f1, intmax_t l1, intmax_t f2, intmax_t l2> 
struct common_type<ranged_integer<f1,l1>, ranged_integer<f2,l2>> 
{ 
    typedef ranged_integer< min(f1,f2) , max(l1,l2) > type; 
}; 

template <class T, class U, class... V> 
struct common_type<T, U, V...> 
{ 
    typedef typename common_type<typename common_type<T, U>::type, V...>::type type; 
}; 

int main(int argc, char *argv[]) 
{ 
    typedef common_type<char, ranged_integer<-99999999, 20>, short, ranged_integer<10, 999999999>, char>::type type; 
    std::cout << type::first << std::endl; // -99999999 
    std::cout << type::last << std::endl; // 999999999 
    return 0; 
} 
2

ngắn

Nếu sử dụng std::common_type theo quy định của thư viện chuẩn là hoàn toàn cần thiết, không có cách nào khác tốt hơn so với 3 lựa chọn thay thế mà bạn quan sát chính mình. Nếu người dùng xác định common_type được coi là chấp nhận được, thì bạn có thể đạt được những gì bạn muốn như được hiển thị bên dưới.


dài trả lời

Bạn đang đúng khi bạn nói std::common_type<unsigned [long [long]] int, [long [long]] int>::type sẽ mang lại unsigned [long [long]]. Tuy nhiên, kể từ khi common_ type của bất kỳ biểu hiện liên quan đến ranged_integer là chính nó một ranged_integer và cho rằng chuyên môn của bạn liên quan đến ranged_integer suy ra một cách chính xác phạm vi, chỉ có một vấn đề nếu các cặp common_type các loại trước [long [long]] unsigned mang [long [long]] int Vững lá chúng ta chỉ sáu trường hợp, chúng tôi đã để khắc phục, cụ thể là ̶ ̶ ̶s̶t̶d̶:̶:̶c̶o̶m̶m̶o̶n̶_̶t̶y̶p̶e̶<̶u̶n̶s̶i̶g̶n̶e̶d̶ ̶[̶l̶o̶n̶g̶ ̶[̶l̶o̶n̶g̶]̶]̶ ̶i̶n̶t̶,̶ ̶[̶l̶o̶n̶g̶ ̶[̶l̶o̶n̶g̶]̶]̶ ̶i̶n̶t̶>̶:̶:̶t̶y̶p̶e̶ ̶ họ và ra lệnh ̶p̶e̶r̶m̶u̶t̶a̶t̶i̶o̶n̶s̶.̶ ̶ (Tôi đang phớt lờ cố định chiều rộng các loại đây, ̶ nhưng mở rộng ý tưởng để xem xét nên được str a̶i̶g̶h̶t̶f̶o̶r̶w̶a̶r̶d̶) ̶

chúng ta có thể đạt được điều đó bởi một lần nữa cung cấp rõ ràng chuyên ngành: ̶

Trong thực tế chúng ta không thể theon3485

[meta.type.synop] Khoản 1

"Hành vi của chương trình bổ sung đặc biệt izations cho bất kỳ mẫu lớp nào được định nghĩa trong điều này [template <class... T> common_type được bao gồm] không được xác định trừ khi được chỉ định khác. "

[meta.trans.other] Bảng 57

[...] Một chương trình thể chuyên đặc điểm này [template <class... T> common_type] nếu ít nhất một mẫu tham số trong chuyên môn là một người dùng định nghĩa kiểu. [...] "

Đó ngụ ý rằng không có cách nào hợp lệ ghi đè lên hành vi cho std::common_type<unsigned [long [long]] int, [long [long]] int>::type, nó là cần thiết bởi các tiêu chuẩn để luôn mang lại unsigned [long [long]] int như chỉ ra trước đó.


Thay thế cho std::common_type

Một thay thế để khắc phục những hạn chế của std::common_type khi áp dụng cho các loại không thể thiếu nguyên thủy, là để xác định một tùy chỉnh common_type.

Giả sử ranged_integer được định nghĩa như sau.

template<typename T, T min, T max> 
struct basic_ranged_integer; 

template<std::intmax_t min, std::intmax_t max> 
using ranged_integer = basic_ranged_integer<std::intmax_t, min, max>; 

Tùy chỉnh common_type có thể được định nghĩa như sau.

Đầu tiên là đệ quy trái:

template<typename... T> 
struct common_type; 

template<typename T, typename U, typename... V> 
struct common_type<T, U, V...> : 
     common_type<typename common_type<T, U>::type, V...> //left recursion 
{}; 

Bây giờ các chuyên ngành liên quan đến basic_ranged_integer.

//two basic_ranged_integer 
template<typename T, T minT, T maxT, typename U, U minU, U maxU> 
struct common_type<basic_ranged_integer<T, minT, maxT>, basic_ranged_integer<U, minU, maxU>> 
{ 
    //gory details go here 
}; 

//basic_ranged_integer mixed with primitive integer types 
//forwards to the case involving two basic_ranged_integer 
template<typename T, T minT, T maxT, typename U> 
struct common_type<basic_ranged_integer<T, minT, maxT>, U> : 
     common_type 
     < 
      basic_ranged_integer<T, minT, maxT>, 
      typename make_ranged_integer<U>::type 
     > 
{}; 

template<typename T, typename U, U minU, U maxU> 
struct common_type<T, basic_ranged_integer<U, minU, maxU>> : 
     common_type 
     < 
      typename make_ranged_integer<T>::type, 
      basic_ranged_integer<U, minU, maxU> 
     > 
{}; 

Và cuối cùng là các chuyên ngành liên quan đến kết hợp các số nguyên nguyên bản và chưa ký.

//base case: forwards to the satandard library 
template<typename T> 
struct common_type<T> : 
     std::common_type<T> 
{}; 

template<typename T, typename U> 
struct common_type<T, U> 
{ 
    static constexpr bool signed_xor = std::is_signed<T>{} xor std::is_signed<U>{}; 

    //base case: forwards to the satandard library 
    template<bool b = signed_xor, typename = void> 
    struct helper : 
      std::common_type<T, U> 
    {}; 

    //mixed signed/unsigned: forwards to the case involving two basic_ranged_integer 
    template<typename _ > 
    struct helper<true, _> : 
      common_type<typename make_ranged_integer<T>::type, typename make_ranged_integer<U>::type> 
    {}; 

    using type = typename helper<>::type; 
}; 

Ở phía trên make_ranged_integer dự kiến ​​sẽ tham gia một kiểu số nguyên nguyên thủy và xác định type là mong muốn tương ứng basic_ranged_integer.

+0

Thật không may, 'OVERWRITE_DEFAULT_INTEGRAL_PROMOTION_RULES (int, long long);' v.v. vẫn không đủ vì có thể có các loại số nguyên mở rộng được định nghĩa thực hiện không thể xử lý theo cách này. Nếu bạn định nghĩa 'common_type' của riêng mình (như gợi ý), bạn có thể dựa vào toán tử'?: 'Và điều chỉnh kết quả của kết quả (thông qua' is_signed' và 'make_signed'). – dyp

+0

@ DyP bạn nói đúng, vì vậy tôi cảnh báo vào lúc bắt đầu câu trả lời của tôi, nhưng vì OP không đề cập đến cơ chế của anh ta là hợp lệ vì không có gì khác với các kiểu nguyên thủy và 'ranged_integer' của anh ấy, và cùng một lúc, muốn 'std :: common_type :: max() + 1>' để làm việc, không có cách nào khác, chúng ta phải đảm bảo 'std :: common_type 'không mang lại' unsigned'. Một lần nữa tôi nên chỉ ra rằng một tùy chỉnh * common-type-like * metafunction sẽ là tốt nhất. – brunocodutra

+0

Ồ, tôi nhớ bạn đã nói "có trường hợp cơ bản chuyển tiếp đến việc triển khai chuẩn" cho tùy chỉnh được đề xuất 'common_type' (điều này sẽ giải quyết vấn đề này). Vâng, sau đó các loại số nguyên mở rộng chỉ cần thêm một caveat thứ hai để chuyên 'std :: common_type' ở đây. – dyp

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