Phương pháp tiếp cận chức năng SFINAE trong C ++


40

Tôi đang sử dụng chức năng SFINAE rất nhiều trong một dự án và không chắc có sự khác biệt nào giữa hai cách tiếp cận sau (ngoài phong cách) không:

#include <cstdlib>
#include <type_traits>
#include <iostream>

template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo()
{
    std::cout << "method 1" << std::endl;
}

template <class T, std::enable_if_t<std::is_same_v<T, double>>* = 0>
void foo()
{
    std::cout << "method 2" << std::endl;
}

int main()
{
    foo<int>();
    foo<double>();

    std::cout << "Done...";
    std::getchar();

    return EXIT_SUCCESS;
}

Đầu ra của chương trình như mong đợi:

method 1
method 2
Done...

Tôi đã thấy phương thức 2 được sử dụng thường xuyên hơn trong stackoverflow, nhưng tôi thích phương thức 1 hơn.

Có trường hợp nào khi hai cách tiếp cận này khác nhau không?


Làm thế nào để bạn chạy chương trình này? Nó sẽ không biên dịch cho tôi.
thay đổi igel

@alter igel nó sẽ cần một trình biên dịch C ++ 17. Tôi đã sử dụng MSVC 2019 để kiểm tra ví dụ này nhưng tôi chủ yếu làm việc với Clang.
keith

Liên quan: why-Should-i-Tránh-stdenable-if-in-function-chữ ký và C ++ 20 cũng giới thiệu những cách mới với khái niệm :-)
Jarod42

@ Jarod42 Khái niệm là một trong những điều cần thiết nhất đối với tôi từ C ++ 20.
val nói Phục hồi lại

Câu trả lời:


35

Tôi đã thấy phương thức 2 được sử dụng thường xuyên hơn trong stackoverflow, nhưng tôi thích phương thức 1 hơn.

Gợi ý: thích phương pháp 2.

Cả hai phương pháp đều hoạt động với các hàm đơn. Vấn đề phát sinh khi bạn có nhiều hơn một hàm, có cùng chữ ký và bạn chỉ muốn kích hoạt một chức năng của tập hợp.

Giả sử rằng bạn muốn kích hoạt tính năng foo(), phiên bản 1, khi bar<T>()(giả vờ đó là một constexprchức năng) là true, và foo(), phiên bản 2, khi bar<T>()false.

Với

template <typename T, typename = std::enable_if_t<true == bar<T>()>>
void foo () // version 1
 { }

template <typename T, typename = std::enable_if_t<false == bar<T>()>>
void foo () // version 2
 { }

bạn gặp lỗi biên dịch vì bạn có một sự mơ hồ: hai foo()hàm có cùng chữ ký (tham số mẫu mặc định không thay đổi chữ ký).

Nhưng giải pháp sau đây

template <typename T, std::enable_if_t<true == bar<T>(), bool> = true>
void foo () // version 1
 { }

template <typename T, std::enable_if_t<false == bar<T>(), bool> = true>
void foo () // version 2
 { }

hoạt động, bởi vì SFINAE sửa đổi chữ ký của các chức năng.

Quan sát không liên quan: cũng có một phương thức thứ ba: bật / tắt kiểu trả về (ngoại trừ các hàm tạo lớp / struct, rõ ràng)

template <typename T>
std::enable_if_t<true == bar<T>()> foo () // version 1
 { }

template <typename T>
std::enable_if_t<false == bar<T>()> foo () // version 2
 { }

Như phương pháp 2, phương pháp 3 tương thích với việc lựa chọn các hàm thay thế có cùng chữ ký.


1
Cảm ơn lời giải thích tuyệt vời, tôi sẽ thích các phương pháp 2 & 3 kể từ bây giờ :-)
keith

"một tham số mẫu mặc định không thay đổi chữ ký" - sự khác biệt này trong biến thể thứ hai của bạn, cũng sử dụng các tham số mẫu mặc định?
Eric

1
@Eric - Không đơn giản để nói ... Tôi cho rằng câu trả lời khác giải thích điều này tốt hơn ... Nếu SFINAE bật / tắt đối số mẫu mặc định, foo()hàm vẫn khả dụng khi bạn gọi nó bằng tham số mẫu thứ hai rõ ràng ( foo<double, double>();cuộc gọi). Và nếu vẫn còn, có một sự mơ hồ với phiên bản khác. Với phương pháp 2, SFINAE bật / tắt đối số thứ hai, không phải tham số mặc định. Vì vậy, bạn không thể gọi nó là khám phá tham số vì có lỗi thay thế không cho phép tham số thứ hai. Vì vậy, phiên bản không có sẵn, vì vậy không có sự mơ hồ
max66

3
Phương pháp 3 có lợi thế bổ sung là thường không rò rỉ vào tên biểu tượng. Biến thể auto foo() -> std::enable_if_t<...>thường hữu ích để tránh ẩn chữ ký hàm và cho phép sử dụng các đối số hàm.
Ded repeatator

@ max66: vì vậy điểm quan trọng là lỗi thay thế trong mặc định tham số mẫu không phải là lỗi nếu tham số được cung cấp và không cần mặc định?
Eric

21

Ngoài câu trả lời của max66 , một lý do khác để thích phương thức 2 là với phương thức 1, bạn có thể (vô tình) vượt qua một tham số loại rõ ràng làm đối số mẫu thứ hai và đánh bại hoàn toàn cơ chế SFINAE. Điều này có thể xảy ra như một lỗi đánh máy, sao chép / dán hoặc là sự giám sát trong một cơ chế mẫu lớn hơn.

#include <cstdlib>
#include <type_traits>
#include <iostream>

// NOTE: foo should only accept T=int
template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo(){
    std::cout << "method 1" << std::endl;
}

int main(){

    // works fine
    foo<int>();

    // ERROR: subsitution failure, as expected
    // foo<double>();

    // Oops! also works, even though T != int :(
    foo<double, double>();

    return 0;
}

Bản demo trực tiếp tại đây


Điểm tốt. Cơ chế có thể bị tấn công.
max66
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.