
On 09/01/2006 06:10 AM, Paul Mensonides wrote:
The rules that we have now for overloading and template argument deduction disallow the following:
template<class T> void f(T&);
int main(void) { f(123); return 0; }
The problem here is that the T cannot be deduced as "const T" which would allow the binding of a temporary to a reference. However, if the argument is *already* a reference to const--instead of a temporary--then the T will be bound to (e.g.) const int. For example:
int main(void) { f((const int&)123); return 0; }
This one isn't a problem. The T will bind to const int and we avoid the temporary problem.
Ah! Thanks. That makes it clearer to me.
What this means is that we can make the problem linear by restricting the input. We don't need every possible combination of cv-qualifiers on various parameters, such as (ignoring volatile):
[snip]
Instead, we only need
template<class T0, class T1> void f(T0&, T1&);
iff the arguments are guaranteed not to be temporaries--namely because the T0 and T1 in the parameter types will be deduced as cv-qualified as necessary.
Given the above, we need a way to cause each argument to preserve it's cv-qualification, yet have temporaries be "promoted" to reference to const. We can do that with overloading similar to what we were doing with the combinatorial overloads--but this time it isn't combinatorial because it is only with one argument at a time--it is linear, but along a different axis (namely cv-qualifications as opposed to arity). Simple way to accomplish that is with identity functions for every possible cv-qualification:
template<class T> T& identity(T&); template<class T> const T& identity(const T&); template<class T> volatile T& identity(volatile T&); template<class T> const volatile T& identity(const volatile T&);
If the identity function is applied to a temporary, it has the same effect as the c-style cast used above:
int main(void) { f(identity(123)); return 0; }
Ah! Now the rational is becoming much clearer.
The advantages of using something like identity are that we don't have to manually try to deduce the type in order to do the cast and that every other kind of argument passes through preserving cv-qualification.
Ultimately then, we can avoid the combinatorial forwarding problem by simply applying identity to each argument before the argument gets the ^should be a 'to'? our function(s). E.g.
template<class T0, class T1> void g(T0&, T1&);
int main(void) { int x = 0; const int y = 0; g(identity(x), identity(y)); g(identity(123), identity(x)); // etc. return 0; } [snip]
The macro interface won't be perfect. You have the usual all-caps library-prefixed macro name, and without variadic macros (C99 + C++0x) you have to specify either the number of arguments or use a different invocation syntax. E.g.
G(2)(x, y) G(2)(123, x)
-or-
G((x)(y)) G((123)(x))
[snip] The 01.09.2006 04:12 ctor_template.zip file in the vault is my attempt at doing what you've outlined above. BTW, instead of something like the G macro, it uses: g( BOOST_FWD_SEQ_FOR_EACH_IDENTITY ( (x) (y) ) ) Please let me know if you see any errors. The newest zip also includes a Makefile which I've found useful in seeing what's produced by cpp. I was wondering how you've gotten bjam to do something similar to that Makefile. If so, could you post the code or a link? Thanks again for a great explanation. -regards, Larry