Sự khác biệt giữa std :: result_of và dectype


100

Tôi gặp khó khăn khi hiểu nhu cầu về std::result_ofC ++ 0x. Nếu tôi hiểu đúng, result_ofđược sử dụng để lấy kiểu kết quả của việc gọi một đối tượng hàm với một số loại tham số nhất định. Ví dụ:

template <typename F, typename Arg>
typename std::result_of<F(Arg)>::type
invoke(F f, Arg a)
{
    return f(a);
}

Tôi không thực sự thấy sự khác biệt với mã sau:

template <typename F, typename Arg>
auto invoke(F f, Arg a) -> decltype(f(a)) //uses the f parameter
{
    return f(a);
}

hoặc là

template <typename F, typename Arg>
auto invoke(F f, Arg a) -> decltype(F()(a)); //"constructs" an F
{
    return f(a);
}

Vấn đề duy nhất tôi có thể thấy với hai giải pháp này là chúng ta cần:

  • có một thể hiện của hàm chức năng để sử dụng nó trong biểu thức được truyền cho kiểu khai báo.
  • biết một hàm tạo được xác định cho functor.

Tôi có ngay trong suy nghĩ rằng sự khác biệt duy nhất giữa decltyperesult_oflà người đầu tiên cần một biểu thức trong khi thứ hai không?

Câu trả lời:


86

result_ofđã được giới thiệu trong Boost , sau đó được đưa vào TR1 , và cuối cùng là trong C ++ 0x. Do đó result_ofcó một ưu điểm là tương thích ngược (với một thư viện phù hợp).

decltype là một thứ hoàn toàn mới trong C ++ 0x, không chỉ giới hạn ở kiểu trả về của một hàm và là một tính năng ngôn ngữ.


Dù sao, trên gcc 4.5, result_ofđược thực hiện theo decltype:

  template<typename _Signature>
    class result_of;

  template<typename _Functor, typename... _ArgTypes>
    struct result_of<_Functor(_ArgTypes...)>
    {
      typedef
        decltype( std::declval<_Functor>()(std::declval<_ArgTypes>()...) )
        type;
    };

4
Theo như tôi hiểu decltypelà xấu hơn nhưng cũng mạnh mẽ hơn. result_ofchỉ có thể được sử dụng cho các kiểu có thể gọi được và nó yêu cầu các kiểu làm đối số. Ví dụ, bạn không thể sử dụng result_ofở đây: template <typename T, typename U> auto sum( T t, U u ) -> decltype( t + u );nếu các đối số có thể là các kiểu số học (không có hàm Fnào để bạn có thể xác định F(T,U)để biểu diễn t+u. Đối với các kiểu do người dùng xác định, bạn có thể làm theo cách tương tự (tôi chưa thực sự chơi với nó) rằng các cuộc gọi đến các phương pháp thành viên có thể là khó để làm với result_ofmà không sử dụng chất kết dính hoặc lambdas
David Rodríguez - dribeas

2
Một lưu ý, kiểu khai báo cần đối số để gọi hàm với AFAIK, vì vậy nếu không có result_of <> thì thật khó xử khi lấy kiểu được trả về bởi mẫu mà không dựa vào đối số có hàm tạo mặc định hợp lệ.
Robert Mason

3
@RobertMason: Những đối số đó có thể được truy xuất bằng cách sử dụng std::declval, giống như mã tôi đã hiển thị ở trên. Tất nhiên, điều này là xấu xí :)
kennytm

1
@ DavidRodríguez-dribeas Nhận xét của bạn có dấu ngoặc đơn mở bằng "(there is" :(
Navin

1
Một lưu ý khác: result_ofvà loại trình trợ giúp của nó result_of_tkhông được chấp nhận kể từ C ++ 17 để ủng hộ invoke_resultinvoke_result_t, giảm bớt một số hạn chế trước đây. Chúng được liệt kê ở cuối trang en.cppreference.com/w/cpp/types/result_of .
sigma

11

Nếu bạn cần loại thứ gì đó không giống như một lệnh gọi hàm, std::result_ofchỉ cần không áp dụng. decltype()có thể cung cấp cho bạn loại biểu thức bất kỳ.

Nếu chúng ta tự giới hạn mình trong các cách khác nhau để xác định kiểu trả về của một lời gọi hàm (giữa std::result_of_t<F(Args...)>decltype(std::declval<F>()(std::declval<Args>()...)), thì sẽ có sự khác biệt.

std::result_of<F(Args...) được định nghĩa là:

Nếu biểu thức INVOKE (declval<Fn>(), declval<ArgTypes>()...)được hình thành tốt khi được coi như một toán hạng không được đánh giá (Điều 5), thì kiểu typedef thành viên sẽ đặt tên kiểu decltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...)); khác, không có kiểu thành viên.

Sự khác biệt giữa result_of<F(Args..)>::typedecltype(std::declval<F>()(std::declval<Args>()...)là tất cả về điều đó INVOKE. Sử dụng declval/ decltypetrực tiếp, ngoài việc nhập lâu hơn một chút, chỉ hợp lệ nếu Fcó thể gọi trực tiếp (kiểu đối tượng hàm hoặc một hàm hoặc một con trỏ hàm). result_ofbổ sung hỗ trợ con trỏ tới các hàm thành viên và con trỏ tới dữ liệu thành viên.

Ban đầu, việc sử dụng declval/ decltypeđảm bảo một biểu thức thân thiện với SFINAE, trong khi đó, bạn std::result_ofcó thể gặp lỗi khó thay vì lỗi khấu trừ. Điều đó đã được sửa chữa trong C ++ 14: std::result_ofbây giờ được yêu cầu phải thân thiện với SFINAE (nhờ bài báo này ).

Vì vậy, trên một trình biên dịch C ++ 14 phù hợp, std::result_of_t<F(Args...)>là hoàn toàn vượt trội. Nó rõ ràng hơn, ngắn gọn hơn và chính xác hỗ trợ nhiều Fs ‡ hơn .


Trừ khi, bạn đang sử dụng nó trong bối cảnh mà bạn không muốn cho phép con trỏ đến các thành viên, vì vậy std::result_of_tsẽ thành công trong trường hợp bạn có thể muốn nó không thành công.

Với các trường hợp ngoại lệ. Mặc dù nó hỗ trợ con trỏ đến các thành viên, nhưng result_ofsẽ không hoạt động nếu bạn cố gắng khởi tạo một type-id không hợp lệ . Chúng sẽ bao gồm một hàm trả về một hàm hoặc nhận các kiểu trừu tượng theo giá trị. Ví dụ.:

template <class F, class R = result_of_t<F()>>
R call(F& f) { return f(); }

int answer() { return 42; }

call(answer); // nope

Cách sử dụng chính xác sẽ là như vậy result_of_t<F&()>, nhưng đó là một chi tiết bạn không cần phải nhớ decltype.


Đối với một loại không tham chiếu Tvà một hàm template<class F> result_of_t<F&&(T&&)> call(F&& f, T&& arg) { return std::forward<F>(f)(std::move(arg)); }, việc sử dụng có result_of_tđúng không?
Zizheng Tai

Ngoài ra, nếu đối số chúng ta truyền đến flà a const T, chúng ta có nên sử dụng result_of_t<F&&(const T&)>không?
Zizheng Tai
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.