1

I'm trying to understand C++ metaprogramming and looking at a type_traits header. But I really can't understand, how std::result_of works. A simple version of result_of is not difficult:

template<class...> struct result_of {};
template <class F, typename... Args> struct result_of<F, Args...> {
    using type = decltype(std::declval<F>()(std::declval<Args>()...));
};

and also is not complete. And I feel stupid, when I see gcc, llvm implementations or implementation provided by cppreference. So how it works and why so difficult?

This is not a practical question, it's a theory question. But... I'm really tried to find an explanation.

edKotinsky
  • 11
  • 3
  • what specifically do you not understand? – 463035818_is_not_an_ai Jan 21 '23 at 13:26
  • Your code is ill-formed. It doesn't work in the first place. A possible implementation (without all the confusing extra stuff you see in actual standard library implementations) is given at https://en.cppreference.com/w/cpp/types/result_of. If you don't understand something specific in it, that could be a suitable question for here. If you generally do not understand the syntax, then you should study it e.g. in a book (https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list also has some intermediate/advanced ones on such topics). – user17732522 Jan 21 '23 at 13:28
  • Also, `std::result_of` has been removed from the language (in C++20). You should use and study `std::invoke_result` instead. – user17732522 Jan 21 '23 at 13:30
  • @user17732522 thank you, I forgot template specialization for the overload. Edited the code. Yes, I know that `std::result_of` is deprecated in C++ 17 and removed. I specially denote a standard in the question title. – edKotinsky Jan 21 '23 at 13:55
  • cppreference says: _Deduces the return type of an INVOKE expression_ and if you check [INVOKE requirements](https://en.cppreference.com/w/cpp/named_req/Callable#Requirements) there are quite a few cases it needs to handle (because the standard requires them to be handled). thus, so many implementation details needed. – dewaffled Jan 21 '23 at 14:01
  • @user17732522 What does this line `MT1 B::*pmf` from a possible implementation mean, namely, "who is who" here. What these abbreviations means? – edKotinsky Jan 21 '23 at 14:04
  • @edKotinsky It is a member pointer, namely `pmf` is declared as a _pointer to member of type `MT1` of class `B`_. This part of the implementation handles invocation of member function pointers. – user17732522 Jan 21 '23 at 14:20
  • @user17732522 One more question. A first overload of a `call` method takes three arguments: `MT1 B::*pmf, T&& t, Args&&... args`, one of them is a parameter pack. But it seems that there are only two arguments at the evaluation: `decltype(invoke_impl::call(std::forward(f), std::forward(args)...))`. Where a `T&&` argument comes from? – edKotinsky Jan 21 '23 at 15:27
  • @edKotinsky It would be the first element of the pack at the call site. – user17732522 Jan 21 '23 at 16:24
  • @user17732522 no, I really can't understand. If `T&&` is a first element of a pack, it shall be the first argument of a function? But then it behaves as if it is an object of class that have a member `pmf`: `invoke_impl::get(std::forward(t)).*pmf`... It is unclear to me. – edKotinsky Jan 22 '23 at 03:08
  • @edKotinsky Yes, because that is exactly how `INVOKE` is specified to work if passed a member function pointer and a reference to an object of class type compatible with it. See the first point under "where INVOKE(f, t1, t2, ..., tN) is defined as follows" at https://en.cppreference.com/w/cpp/named_req/Callable#Requirements. – user17732522 Jan 22 '23 at 09:33
  • Learning about meta programming is commendable, but as with any subjects, it's better to start with the easiest before moving on to more difficult sub-topics. STL implementations are probably among the most difficult templates to read. The STL must work with for a very wide range of inputs, and must be optimized for some corner cases. – Michaël Roy Jan 22 '23 at 19:11
  • @user17732522 Yes, I see that the first template parameter - function argument is a class, which has a member. Below, is the example code, there is a piece of code: `std::result_of::type`. Maybe it is a stupid question, but why? If I understanding properly, the template specialization of an `invoke_impl` knows about a class: `MT B::*`. What is the purpose of passing it as a function argument? – edKotinsky Jan 23 '23 at 14:24
  • @MichaëlRoy Yes, STL is not easy, I see. Can you recommend me some additional resources about template metaprogramming, if – edKotinsky Jan 23 '23 at 14:47
  • @edKotinsky It gives you the type of `std::invoke(&C::Func, C{}, 'a', x)` (where `x` is an `int`). When you invoke a member function pointer of class `C` you need an instance of class `C`. So if `std::invoke` is supposed to support member function pointers, then it must be given an instance of `C` as some argument. `std::invoke_result` should determine the type you get from an `std::invoke` call. – user17732522 Jan 23 '23 at 15:15
  • @user17732522 Thank you. I feel more knowing this thing, maybe I will write an answer with my understanding of it. But one more question: [standard says](https://en.cppreference.com/w/cpp/named_req/Callable#Requirements), that pointers to data member are callable, even no function calls takes place. What does this mean? How you can call a non-callable data member? – edKotinsky Jan 24 '23 at 14:06
  • @edKotinsky It doesn't say that a data member can be _called_. It says that a pointer-to-data-member satisfies the abstract concept named _Callable_. That's a choice the language designed made when designing the _Callable_ concept and `INVOKE`/`std::invoke`. What `INVOKE` means for a pointer-to-data-member is explained on the linked page. No function call is involved. – user17732522 Jan 24 '23 at 14:44
  • @user17732522 OK, it seems I got it: it just deduces the type of a data member. There are three cases: 1) If F is a pointer to data member, and the first argument `t1` is a class which is derived from T, then the type is `t1.*f`. 2) If `t1` is a `std::reference_wrapper`, then get a type which is stored in it and deduce a type of its member. 3) In other cases (if t1 is just a some class), the type is `t1.f`. Here `INVOKE(f, t1)` f is a pointer, and t1 is a class/child class/ref wrapper. Is it right? – edKotinsky Jan 24 '23 at 15:29

0 Answers0