2017-12-19 101 views
5

Thư viện chuẩn C++ có std::is_constructible<Class, T...> để kiểm tra xem một lớp có thể được xây dựng từ các kiểu đã cho làm đối số hay không.Loại đặc điểm cho khả năng khởi tạo tổng hợp trong thư viện chuẩn?

Ví dụ: nếu tôi có một lớp MyClass có một hàm tạo MyClass(int, char), thì std::is_constructible<MyClass, int, char>::value sẽ là true.

Có đặc điểm kiểu thư viện chuẩn tương tự nào sẽ kiểm tra xem việc khởi tạo tổng hợp có hoạt động hay không, tức là MyClass{int, char} được tạo đúng và trả về MyClass?

trường hợp sử dụng của tôi:

Tôi muốn viết một hàm mẫu chuyển đổi một std::tuple đến một (thường là POD) lớp sử dụng khởi tạo tổng hợp, một cái gì đó với các chữ ký sau:

template <typename Class, typename... T> 
inline Class to_struct(std::tuple<T...>&& tp); 

Trong để ngăn người dùng sử dụng chức năng này với Class không hợp lệ, tôi có thể viết static_assert bên trong chức năng này để kiểm tra xem thông số tp đã cho có các loại chuyển đổi thành thành viên củahay không. Có vẻ như một đặc điểm kiểu như is_aggregate_initializable<Class, T...> sẽ có ích.

Tôi có thể triển khai thực hiện đặc điểm này, nhưng chỉ để biết thông tin, có một đặc điểm trong thư viện chuẩn mà tôi đã bỏ qua hay sớm trở thành một phần của thư viện chuẩn không?

+1

Tôi không thấy lý do tại sao nó sẽ hữu ích. 'std :: is_constructible' khá nhiều tồn tại để mã chung, đối với một loại như' std :: vector ', có thể tránh việc khởi tạo danh sách do tai nạn. Tại sao bạn không thể làm 'MyClass {Args ...}' và cần phải quan tâm đến điều này là tổng hợp hay không? – StoryTeller

+0

@StoryTeller Trong mã tổng quát hơn, 'to_struct()' sẽ hoạt động miễn là 'std :: tuple_size' và' std :: tuple_element' được định nghĩa, vì vậy nó sẽ giống như 'template inline Class to_struct (Tuple && tp); ', với các hàm thực thi nội bộ thích hợp không dựa vào' std :: tuple'. Sau đó, ví dụ, tôi có thể muốn có một quá tải khác 'to_struct()' bao bọc 'Lớp' xung quanh đối tượng nhất định, * không có * giải nén (chỉ khi nó không thể giải nén) Trong trường hợp này, tôi sẽ cần phải hạn chế quá tải đầu tiên (có thể sử dụng các công cụ SFINAE) bằng cách sử dụng đặc điểm kiểu. – Bernard

+0

@Bernard: "* Tôi có thể cuộn việc thực hiện của riêng tôi về đặc điểm này * "Không, bạn không thể. Không có đặc tính' is_aggregate', không có cách nào bạn có thể biết sự khác biệt giữa 'aggregate {1, 2}' working và 'non_aggregate {1, 2}' –

Trả lời

1

Từ cuộc thảo luận trong các nhận xét và duyệt tham chiếu C++, có vẻ như có một đặc điểm kiểu thư viện chuẩn không thể khởi tạo tổng hợp hoặc không thể liệt kê khả năng khởi tạo, ít nhất tối đa C++ 17.

Được đánh dấu trong các nhận xét rằng có sự khác biệt giữa list initializability nói chung (Class{arg1, arg2, ...}) và aggregate initializability.

Danh sách initializability (đặc biệt trực tiếp danh sách initializability) là dễ dàng hơn để viết một loại đặc điểm cho vì đặc điểm này là hoàn toàn phụ thuộc vào tính hợp lệ của một cú pháp nhất định. Đối với trường hợp sử dụng thử nghiệm của tôi nếu một cấu trúc có thể được xây dựng từ các phần tử của một bộ, thì khả năng khởi tạo danh sách trực tiếp có vẻ thích hợp hơn.

Một cách có thể để thực hiện đặc điểm này (với SFINAE thích hợp) là như sau:

namespace detail { 
    template <typename Struct, typename = void, typename... T> 
    struct is_direct_list_initializable_impl : std::false_type {}; 

    template <typename Struct, typename... T> 
    struct is_direct_list_initializable_impl<Struct, std::void_t<decltype(Struct{ std::declval<T>()... })>, T...> : std::true_type {}; 
} 

template <typename Struct, typename... T> 
using is_direct_list_initializable = detail::is_direct_list_initializable_impl<Struct, void, T...>; 

template<typename Struct, typename... T> 
constexpr bool is_direct_list_initializable_v = is_direct_list_initializable<Struct, T...>::value; 

Sau đó, chúng ta có thể kiểm tra danh sách initializability trực tiếp bằng cách làm is_direct_list_initializable_v<Class, T...>.

Điều này cũng phù hợp với ngữ nghĩa di chuyển và chuyển tiếp hoàn hảo, bởi vì std::declval tôn trọng các quy tắc chuyển tiếp hoàn hảo.

Tổng hợp khả năng khởi tạo ít đơn giản hơn, nhưng có giải pháp bao gồm hầu hết các trường hợp. Khởi tạo tổng hợp yêu cầu loại được khởi tạo là tổng hợp (xem giải thích trên C++ reference on aggregate initialization) và chúng tôi có đặc điểm C++ 17 std::is_aggregate để kiểm tra xem loại có phải là tổng hợp không.

Tuy nhiên, điều đó không có nghĩa là chỉ vì loại là tổng hợp, việc khởi tạo danh sách trực tiếp thông thường sẽ không hợp lệ. Khởi tạo danh sách bình thường phù hợp với các nhà xây dựng vẫn được cho phép. Ví dụ, biên dịch sau:

struct point { 
    int x,y; 
}; 

int main() { 
    point e1{8}; // aggregate initialization :) 
    point e2{e1}; // this is not aggregate initialization! 
} 

Để không cho phép loại danh sách khởi tạo, chúng ta có thể sử dụng thực tế là tập hợp không thể có tùy chỉnh (tức là người dùng cung cấp) nhà xây dựng, vì vậy không tổng khởi phải chỉ có một tham số và Class{arg} sẽ đáp ứng std::is_same_v<Class, std::decay_t<decltype(arg)>>.

May mắn thay, we can't have a member variable of the same type as its enclosing class, vì vậy sau đây là không hợp lệ:

struct point { 
    point x; 
}; 

Có một caveat này: loại tham chiếu đến cùng một đối tượng được phép bởi vì tài liệu tham khảo thành viên có thể loại không đầy đủ (GCC, Clang, và MSVC tất cả chấp nhận điều này mà không có bất kỳ cảnh báo nào):

struct point { 
    point& x; 
}; 

Trong khi khác thường, this code is valid by the standard. Tôi không có giải pháp để phát hiện trường hợp này và xác định rằng point là tổng hợp có thể khởi tạo với đối tượng thuộc loại point&.

Bỏ qua sự báo trước trên (nó hiếm khi cần phải sử dụng một loại như vậy), chúng ta có thể đưa ra một giải pháp mà sẽ làm việc:

template <typename Struct, typename... T> 
using is_aggregate_initializable = std::conjunction<std::is_aggregate<Struct>, is_direct_list_initializable<Struct, T...>, std::negation<std::conjunction<std::bool_constant<sizeof...(T) == 1>, std::is_same<std::decay_t<std::tuple_element_t<0, std::tuple<T...>>>, Struct>>>>; 

template<typename Struct, typename... T> 
constexpr bool is_aggregate_initializable_v = is_aggregate_initializable<Struct, T...>::value; 

Nó không trông rất đẹp, nhưng không chức năng như mong đợi .

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