Confusion about what actually happens with this decltype statement
So, I looked at http://en.cppreference.com/w/cpp/types/result_of and saw the syntax for executing a result_of
member function and I just don't know what is going on with this declaration.
Why do arguments appear after decltype? Wouldn't they be important when determining the type of a member function? In my opinion, I suppose decltype(&C::Func)(C, char, int&)
it should be decltype(&C::Func(C, char, int&))
or something instead , but I have a hard time wrapping my head around me. Can someone explain why this particular syntax?
source to share
std::result_of
takes a templated form argument F(A...)
. F
must be a type that can be called, such as a function type or an overloaded class type operator()
. A...
must be a sequence of argument types.
So if you have an expression e
and some argument types A...
and you want to know what type of result you get if you call e
with type arguments A...
, then you should put F
= decltype(e)
in std::result_of<F(A...)>
, that is std::result_of<decltype(e)(A...)>
.
source to share
I am copying the relevant code from the example you pointed to:
#include <type_traits>
struct C {
double Func(char, int&);
};
int main()
{
// result_of can be used with a pointer to member function as follows
std::result_of<decltype(&C::Func)(C, char, int&)>::type g = 3.14;
static_assert(std::is_same<decltype(g), double>::value, "");
}
-
decltype(&C::Func)
is the declared type of the methodFunc
ofC
, which is a function that takes a referenceC
(matchingthis
), achar
andint
. - Let's call this type
T
. - Then it
result_of<T(...)>::type
will be the type of the result of applying the type functionT
to the arguments, the types of which you specify in parentheses. - Therefore, this example
result_of<decltype(&C::Func)(C, char, int&)>::type
willdouble
.
source to share
As you know, type T1(T2,T3)
means a function that returns a value T1
and takes arguments of type T2
and T3
. Once you work with values โโof this type, you owe this interpretation.
std::result_of
has nothing to do with any values, just the type of the form T1(T2,T3)
, so he technically has the right to interpret types in any way he likes. And it really is! If it std::result_of
is parameterized by the type of a function and returns the return type of that function, the result (i.e. the nested member type
) is simple T1
, but it is not. The standard authors decided to implement a different functionality: std::result_of
taking a type T1
in it, the type parameter is not the return type of the function to be defined, but the full type of some callable (like a pointer to a function), and it will return the type returned by this called thing when arguments are passed T1
and T2
...
Example time!
#include <iostream>
#include <ostream>
#include <type_traits>
#include <typeinfo>
int main(void)
{
// define the type of a function
typedef int ftype(char, long);
// (A) doesn't compile: you can't call an int
std::cout << typeid(std::result_of<int(char,long)>).name() << '\n';
// (B) doesn't compile: A the return type of a function may not be a function
std::cout << typeid(std::result_of<ftype(char,long)>::type).name() << '\n';
// (C) does compile and print the typeid name of int. On g++ this is "i"
std::cout << typeid(std::result_of<ftype*(char,long)>::type).name() << '\n';
}
Case (A) fails because int
it is not callable, although the boilerplate itself is well formed. Case (B) fails because the boilerplate is not valid. B T1(T2,T3)
, T1
must not be a function type, since types describing a function returning a function are not allowed. Case (C) has a valid templated parameter in which the return type of the "function" describes the type to be called, so it is std::result_of
applicable.
With this context in mind, the answer to your question is likely to be obvious. The expression decltype(&C::Func)(C, char, int&)
describes the type of the function that returns decltype(&C::Func)
and accepts parameter types C
, char
and int &
. As discussed, the return type must be something callable , which is the case decltype(&C::Func)
, since it is a pointer to -member-function type double (C::*)(char, int&)
. According to the INVOKE operation definition (see the page about the callee), this type can be invoked using a parameter list (C, char, int&)
, so the application std::result_of
is valid decltype(&C::Func)(C, char, int&)
.
Alternative: std::result_of<decltype(&C::Func(C, char, int&))>
Invalid because it is &C::Func(C, char, int&)
not a valid expression. If you want to constrain the type (in the case of multiple overloads Func
), you can do so using a cast. decltype(static_cast<double (C::*)(int, char&)>(&C::Func))
is a valid expression that returns (not surprisingly) a type double (C::*)(int, char&)
. But as in example (A), this is not a type that you can use std::result_of
on.
The really interesting use case for std :: result_of is when the T1
type being called is a function object. By passing the type of an object to a function std::result_of
as T1
, you are passing all the call statements of that object at the same time, and you can choose the std::result_of
correct one using overload resolution. Passing "all functions Func
" as you pass "all functions is operator()
not possible because it std::result_of
hardcodes operator()
in the case of objects, and you cannot use an operator address to map operator()
calls to Func()
invocations. You can write a template that does this matching, though:
#include <iostream>
#include <ostream>
#include <type_traits>
#include <typeinfo>
class S {
public:
int Func(int);
double Func(float, float);
};
template <typename T>
class call_Func : public T {
public:
template<typename... args>
auto operator()(args... vals) -> decltype(this->Func(vals...)) { return this->Func(vals...); }
};
int main(void)
{
std::cout << typeid(std::result_of<call_Func<S>(int)>::type).name() << '\n';
}
The call_Func template redirects the call operator()
to call_Func<S>
to a call to Func in the base class S
(should be used however std::forward
), but note that you cannot write a "generic redirector" that takes the name of the function to redirect the function call statement as a template parameter, since you cannot neither pass overloading set nor names as template parameters, but just types and constant values โโ(for non-type parameters). Member pointer functions are a kind of constant value, but you've already lost the overload as soon as you form such a pointer.
source to share