why some callables require std::forward and others don't?

Hi,

I am grabbing this code from cppreference.com and cannot understand why std::forward is used sometimes on the callable and sometimes it isn't...


For instance see the confusing treatment (not uniform) of t1 in the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
	template<typename C, typename Pointed, typename T1, typename...Args >
	constexpr decltype(auto) invoke_memptr(Pointed C::*f, T1&& t1, Args&&...args)
	{
		if constexpr (std::is_function_v<Pointed>)
		{
			if constexpr (std::is_base_of_v<C, std::decay_t<T1>>)
			{
				return (std::forward<T1>(t1).*f)(std::forward<Args>(args)...);
			}
			else if constexpr (is_reference_wrapper_v<std::decay_t<T1>>)
			{
				return (t1.get().*f)(std::forward<Args>(args)...);
			}
			else
			{
				return (*std::forward<T1>(t1)).*f(std::forward<Args>(args)...);
			}
		}
		else
		{
			static_assert(std::is_object_v<Pointed> && sizeof...(Args) == 0);
			if constexpr (std::is_base_of_v<C, std::decay_t<T1>>)
			{
				return std::forward<T1>(t1).*f;
			}
			else if constexpr (is_reference_wrapper_v<std::decay_t<T1>>)
			{
				return t1.get().*f;
			}
			else
			{
				return (*std::forward<T1>(t1)).*f;
			}
		}
	}



Thank you!

Juan
I am grabbing this code from cppreference.com and cannot understand why std::forward is used sometimes on the callable and sometimes it isn't...

The Callable is named f and it is never forwarded in the above. f is a pointer-to-member and t1 is the first argument to the Callable.

t1 is forwarded whenever its value category could affect the behavior of the INVOKE operation.
It's forwarded unless the parameter t1 is a reference to reference_wrapper. In that case t1 is the implicit object argument to std::reference_wrapper<T>::get. And there t1.get() is always an lvalue expression with type T, no matter T, no matter the value category of t1, and no matter any template specializations. So there's no need to forward it.
Last edited on
I am grabbing this code from cppreference.com

https://en.cppreference.com/w/cpp/utility/functional/invoke
thanks!
[output]
t1 is forwarded whenever its value category could affect the behavior of the INVOKE operation.
[/output/

this would be the case when T1 provides 2 implementations of the function whose only difference is that one is called for lvalue reference and the other for rvalue reference?

lik this:

1
2
3
4
5
struct T1
{
     int DoSomething() & { return 12; }
     int DoSomething() && { return 22; }
}


is this correctly interpreted?


Essentially, although the "function" being called (the Callable) is f, not t1, it's still possible to make a t1 where value category makes a difference.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <type_traits>
#include <functional>
#include <cstdio>

template <typename T> constexpr bool is_reference_wrapper_v = false; // TODO(mbozzi): stub

template<typename C, typename Pointed, typename T1, typename...Args >
	constexpr decltype(auto) invoke_memptr(Pointed C::*f, T1&& t1, Args&&...args)
	{
		if constexpr (std::is_function_v<Pointed>)
		{
			if constexpr (std::is_base_of_v<C, std::decay_t<T1>>)
			{
				return (std::forward<T1>(t1).*f)(std::forward<Args>(args)...);
			}
			else if constexpr (is_reference_wrapper_v<std::decay_t<T1>>)
			{
				return (t1.get().*f)(std::forward<Args>(args)...);
			}
			else
			{ // Typo fixed here, missing parentheses
				return ((*std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);
			}
		}
		else
		{
			static_assert(std::is_object_v<Pointed> && sizeof...(Args) == 0);
			if constexpr (std::is_base_of_v<C, std::decay_t<T1>>)
			{
				return std::forward<T1>(t1).*f;
			}
			else if constexpr (is_reference_wrapper_v<std::decay_t<T1>>)
			{
				return t1.get().*f;
			}
			else
			{
				return (*std::forward<T1>(t1)).*f;
			}
		}
	}
	
struct A1 { void f() {} };
struct A2 { void f() & {} };

struct T1
{
  A1 a1;
  A1& operator*() & noexcept { std::puts("A& operator*() & noexcept"); return a1; }
  A1&& operator*() && noexcept { std::puts("A& operator*() && noexcept"); return std::move(a1); }    
};

int main()
{
  T1 t1; 
  invoke_memptr(&A1::f, t1);
  invoke_memptr(&A1::f, T1{});
  
  A2 a2;
  invoke_memptr(&A2::f, a2);
  // invoke_memptr(&A2::f, A2{}); // error, lvalue required
}
Registered users can post here. Sign in or register to post.