Câu hỏi Làm cho `std :: get` trở nên tuyệt vời với SFINAE


std::get dường như không thân thiện với SFINAE, như được minh họa trong trường hợp kiểm tra sau:

template <class T, class C>
auto foo(C &c) -> decltype(std::get<T>(c)) {
    return std::get<T>(c);
}

template <class>
void foo(...) { }

int main() {
    std::tuple<int> tuple{42};

    foo<int>(tuple);    // Works fine
    foo<double>(tuple); // Crashes and burns
}

Xem nó trực tiếp trên Coliru

Mục tiêu là chuyển hướng cuộc gọi thứ hai sang foo về phía quá tải thứ hai. Trong thực tế, libstdc ++ cho:

/usr/local/bin/../lib/gcc/x86_64-pc-linux-gnu/6.3.0/../../../../include/c++/6.3.0/tuple:1290:14: fatal error: no matching function for call to '__get_helper2'
    { return std::__get_helper2<_Tp>(__t); }
             ^~~~~~~~~~~~~~~~~~~~~~~

libc ++ trực tiếp hơn, trực tiếp static_assert nổ:

/usr/include/c++/v1/tuple:801:5: fatal error: static_assert failed "type not found in type list"
    static_assert ( value != -1, "type not found in type list" );
    ^               ~~~~~~~~~~~

Tôi thực sự không muốn thực hiện các lớp hành tây kiểm tra xem C là một std::tuple chuyên môn và đang tìm kiếm T bên trong các thông số của nó ...

Có lý do nào cho std::get không thân thiện với SFINAE? Có cách giải quyết tốt hơn so với những gì được nêu ở trên không?

tôi đã tìm thấy một cái gì đó về std::tuple_element, nhưng không std::get.


13
2018-01-17 22:53


gốc


Phần "S" của "SFINAE" là viết tắt của "thay thế". Ở đây, không có vấn đề gì thay thế các tham số mẫu vào foo. T Là double. C là loại tuple. Thay thế thành công! - Sam Varshavchik
@SamVarshavchik nhưng kiểu trả về liên quan đến việc tạo thành một cuộc gọi hàm không hợp lệ. Mà sẽ gây ra một sự thất bại thay thế, nếu std::get đã thân thiện với SFINAE (ví dụ: tự thoát khỏi quá tải cho cuộc gọi không hợp lệ). Do đó câu hỏi của tôi. - Quentin
@SamVarshavchik xem đoạn mã này Ví dụ. - Quentin
Khi nào std::gettham số mẫu là một lớp, kiểu trả về của nó là rõ ràng, chứ không phải là auto, như trường hợp của hằng số chỉ mục tuple. Vì thế, decltype() hạnh phúc. template< class T, class... Types > constexpr T& get(tuple<Types...>& t); - trò chơi kết thúc. - Sam Varshavchik
@SamVarshavchik Bạn đang làm gì thế. - Barry


Các câu trả lời:


std::get<T> rõ ràng là không thân thiện với SFINAE, theo [tuple.elem]:

template <class T, class... Types>
  constexpr T& get(tuple<Types...>& t) noexcept;
// and the other like overloads

Đòi hỏi: Loại T xảy ra chính xác một lần trong Types.... Nếu không, chương trình bị hỏng.

std::get<I> cũng rõ ràng không thân thiện với SFINAE.


Theo như các câu hỏi khác:

Có lý do nào cho std::get không thân thiện với SFINAE?

Không biết. Thông thường, đây không phải là điểm cần được SFINAE-ed bật. Vì vậy, tôi đoán nó không được coi là một cái gì đó mà cần phải được thực hiện. Các lỗi cứng dễ hiểu hơn rất nhiều so với việc cuộn qua một loạt các tùy chọn ứng viên không khả thi. Nếu bạn tin rằng có lý do thuyết phục cho std::get<T> để được SFINAE thân thiện, bạn có thể gửi một vấn đề LWG về nó.

Có cách giải quyết tốt hơn so với những gì được nêu ở trên không?

Chắc chắn rồi. Bạn có thể viết nhà riêng của SFINAE thân thiện với bạn get (xin lưu ý, nó sử dụng C ++ 17 biểu thức gấp):

template <class T, class... Types,
    std::enable_if_t<(std::is_same<T, Types>::value + ...) == 1, int> = 0>
constexpr T& my_get(tuple<Types...>& t) noexcept {
    return std::get<T>(t);
}

Và sau đó làm với điều đó như bạn muốn.


16
2018-01-17 23:38



Tự do chỉnh sửa câu trả lời để đề cập đến mã C ++ 17 này. - SergeyA
@SergeyA Cảm ơn! - Barry


Không SFINAE vào std::get; điều đó không được phép.

Dưới đây là hai cách tương đối thân thiện với sfinae để kiểm tra nếu bạn có thể using std::get; get<X>(t):

template<class T,std::size_t I>
using can_get=std::integral_constant<bool, I<std::tuple_size<T>::value>;

namespace helper{
  template<class T, class Tuple>
  struct can_get_type:std::false_type{};
  template<class T, class...Ts>
  struct can_get_type<T,std::tuple<Ts...>>:
    std::integral_constant<bool, (std::is_same_v<T,Ts>+...)==1>
  {};
}
template<class T,class Tuple>
using can_get=typename helpers::can_get_type<T,Tuple>::type;

Sau đó, mã của bạn đọc:

template <class T, class C, std::enable_if_t<can_get_type<C,T>{},int> =0>
decltype(auto) foo(C &c) {
  return std::get<T>(c);
}

5
2018-01-18 01:38





Từ N4527 (tôi đoán nó vẫn còn trong tiêu chuẩn):

§ 20.4.2.6 (8):

Yêu cầu: Kiểu T xảy ra chính xác một lần trong các loại .... Nếu không, chương trình sẽ bị hỏng.

Chương trình trên không đúng chuẩn, theo tiêu chuẩn.

Kết thúc cuộc thảo luận.


3
2018-01-17 23:37