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?

+3


source to share


3 answers


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...)>

.

+3


source


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 method Func

    of C

    , which is a function that takes a reference C

    (matching this

    ), a char

    and int

    .
  • Let's call this type T

    .
  • Then it result_of<T(...)>::type

    will be the type of the result of applying the type function T

    to the arguments, the types of which you specify in parentheses.
  • Therefore, this example result_of<decltype(&C::Func)(C, char, int&)>::type

    will double

    .
0


source


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.

0


source







All Articles