Summary: I want to end up with a function that deduces the exact types it was called with and takes (e.g.) a tuple that forwards them (the types of which will be different from the exact types the function was called with).
I'm stuck trying to "know" via deduction the types of the arguments to a given function, whilst simultaneously forwarding them. I think I might be missing something crucial about how this works.
#include <tuple>
#include <string>
#include <functional>
template <typename ...Args>
struct unresolved_linker_to_print_the_type {
unresolved_linker_to_print_the_type();
};
void f(int,double,void*,std::string&,const char*) {
}
template <typename F, typename ...Args>
void g1(F func, Args&&... args) {
unresolved_linker_to_print_the_type<Args...>();
auto tuple = std::forward_as_tuple(args...);
unresolved_linker_to_print_the_type<decltype(tuple)>();
}
template <typename F, typename T, typename ...Args>
void g2(F func, const T& tuple, Args... args) {
unresolved_linker_to_print_the_type<Args...>();
unresolved_linker_to_print_the_type<decltype(tuple)>();
}
int main() {
int i;
double d;
void *ptr;
std::string str;
std::string& sref = str;
const char *cstr = "HI";
g1(f, i,d,ptr,sref,cstr);
g2(f, std::forward_as_tuple(i,d,ptr,sref,cstr), i,d,ptr,sref,cstr);
}
What I'd like to see is a scenario where when my function (e.g. g1
or g2
) gets called it knows and can use both the original types - int,double,void*,std::string&,const char*
and the forwarded arugments too.
In this instance I don't seem to be able to find this information from within g1
or g2
. The (deliberate, to print out the types) linker error shows me in g1
they are:
int&, double&, void*&, std::string&, char const*&
int&, double&, void*&, std::string&, char const*&
and in g2
:
int, double, void*, std::string, char const*
int&, double&, void*&, std::string&, char const*&
There are two thing I don't get here:
Why do none of the printed (via the linker error) types match what I actually passed in? (int,double,void*,std::string&,const char
). Can I deduce what I actually was passed? Preferably with "natural" syntax, i.e. everything just once and nothing explicitly written out. I can explicitly write:
g2<decltype(&f),decltype(std::forward_as_tuple(i,d,ptr,sref,cstr)),int,double,void*,std::string&,const char*>(f,std::forward_as_tuple(i,d,ptr,sref,cstr),i,d,ptr,sref,cstr);
but that's "unwieldy" to say the least!
In g1
the presence of &&
in the function signature declaration seems to alter the types in the template parameter Args
itself. Compare that with:
template <typename T>
void test(T t);
Or:
template <typename T>
void test(T& t);
using either of those with:
int i;
test(i);
doesn't change the type of T
. Why does the &&
change the type of T
itself when &
doesn't?
The auditor should consider the size of the entity, the entity's organizational structure, the nature of its operations, the types, frequency, and complexity of its derivatives and securities transactions, and its controls over those transactions in designing auditing procedures for assertions about derivatives and ...
Steps before Commencement of new Audit 1. Appointment Confirmation The auditor must examine the terms and conditions of his appointment. It should be confirmed that the appointment was authorized by a competent authority and there is no reason whatsoever in rejecting this offer. 2.
The reliability of evidence depends on the nature and source of the evidence and the circumstances under which it is obtained. For example, in general: Evidence obtained from a knowledgeable source that is independent of the company is more reliable than evidence obtained only from internal company sources.
Answer to first question:
Arguments to functions are expressions, not types. The difference between these two is expressed in chapter 5 [expr], p5:
If an expression initially has the type “reference to T” (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis.
Thus, there is no difference what-so-ever between g(str)
and g(sref)
. g()
always sees a std::string
, and never a reference.
Additionally expressions can be lvalue or rvalue (actually that's a simplification of the C++11 rules, but it is close enough for this discussion - if you want the details they're in 3.10 [basic.lval]).
Answer to second question:
Template parameters of the form:
template <class T>
void g(T&&);
are special. They are unlike T
, T&
, or even const T&&
in the following way:
When T&&
binds to an lvalue, T
is deduced as an lvalue reference type, otherwise T
deduces exactly as per the normal deduction rules.
Examples:
int i = 0;
g(i); // calls g<int&>(i)
g(0); // calls g<int>(0)
This behavior is to support so called perfect forwarding which typically looks like:
struct A{};
void bar(const A&);
void bar(A&&);
template <class T>
void foo(T&& t)
{
bar(static_cast<T&&>(t)); // real code would use std::forward<T> here
}
If one calls foo(A())
(an rvalue A
), T
deduces per normal rules as A
. Inside of foo
we cast t
to an A&&
(an rvalue) and call bar
. The overload of bar
that takes an rvalue A
is then chosen. I.e. if we call foo
with an rvalue, then foo
calls bar
with an rvalue.
But if we call foo(a)
(an lvalue A
), then T
deduces as A&
. Now the cast looks like:
static_cast<A& &&>(t);
which under the reference collapsing rules simplifies to:
static_cast<A&>(t);
I.e. the lvalue t
is cast to an lvalue (a no-op cast), and thus the bar
overload taking an lvalue is called. I.e. if we call foo
with an lvalue, then foo
calls bar
with an lvalue. And that's where the term perfect forwarding comes from.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With