Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C++ > Template argument deduction on integer literals

Reply
Thread Tools

Template argument deduction on integer literals

 
 
Bart Samwel
Guest
Posts: n/a
 
      04-22-2005
Hi everybody,

I would really like some help explaining this apparent discrepancy,
because I really don't get it. Here is the snippet:


void foo(int&);
void foo(int const&);

template<typename T>
void bar(T&);

int main()
{
foo(10); // calls foo(const int&)
bar(10); // calls bar<int>(int&)
}

The discrepancy is that apparently in the call to bar(), "10" is
interpreted as a const int, while in the call to bar(), "10" is
interpreted as a _non-const_ int. This behaviour is seen in g++ 3.3.5,
MSVC++ 7.1 and Comeau. To make things worse, the second call is then
always _rejected_ by the compiler, because (quoting MSVC++ 7.1):
"cannot convert parameter 1 from 'int' to 'int &': A reference that is
not to 'const' cannot be bound to a non-lvalue". So why is the "const
int" overload selected when no templates are involved, but the "int"
overload with the template?



For those interested, a bit of context on how I ran into this. In
MSVC++ 6.0 we had a "pass-through" construct for function arguments,
which boiled down to something like this:

template<typename T, typename T1, typename T2, typename T3>
void my_new(T1& a1, T2& a2, T3& a3)
{
/* ... */
new T(a1, a2, a3)
/* ... */
}

The reason we needed this was because we wanted to "wrap" the
constructed object into some other object (in our case, a container for
reference counting information) and only return a reference to that
object, but we still needed to pass constructor parameters. *Any* set
of constructor parameters, because it was a completely generic system.
So, we had a function template like this for any number of arguments
between 0 and 20, and it worked perfectly. If we would do something
like my_new<Foo>(10,20,30), then MS VC++ 6.0 would use my_new<Foo,const
int,const int,const int>. But now that we're trying this in newer, more
compliant compilers, it suddenly doesn't work, and we haven't found
another simple way of building a "generic argument passthrough" that
works. The only solution we've found is to add overloads for any
combination of const and non-const parameters:

template<typename T, typename T1>
void my_new(T1& a1)
{
}
template<typename T, typename T1>
void my_new(const T1& a1)
{
}

int main()
{
// This calls my_new<Foo,int>(const int&) -- even though the
// template argument is deduced as non-const int, the const
// overload is then selected. Again, WHY?
my_new<Foo>(10);
}

But the number of const/non-const combinations rises exponentially with
the number of arguments, so it's already not feasible to do this for
the versions with over five arguments. So, we're basically out of a
good way to do generic passthrough. If anybody has any ideas on how we
could simulate a decent passthrough (that preserves reference
semantics, no copies allowed, and that selects the right overload when
passing the data on to another function!) I'd be much obliged...

Regards,
Bart Samwel

 
Reply With Quote
 
 
 
 
Jean-Sebastien Samson
Guest
Posts: n/a
 
      04-22-2005
> Hi everybody,
>
> I would really like some help explaining this apparent discrepancy,
> because I really don't get it. Here is the snippet:
>
>
> void foo(int&);
> void foo(int const&);
>
> template<typename T>
> void bar(T&);
>
> int main()
> {
> foo(10); // calls foo(const int&)
> bar(10); // calls bar<int>(int&)
> }


Your compiler gives the appropriate message "cannot convert parameter 1 from
'int' to 'int &'". A basic_type constant (and not a basic_type variable)
cannot be bound to a lvalue then it cannot convert 10 to int& hence the
selection of foo(int const &) and that bar won't work.
--
JS


 
Reply With Quote
 
 
 
 
Bart Samwel
Guest
Posts: n/a
 
      04-22-2005
Hi Jean-Sebastien,

> Your compiler gives the appropriate message "cannot convert parameter

1 from
> 'int' to 'int &'". A basic_type constant (and not a basic_type

variable)
> cannot be bound to a lvalue then it cannot convert 10 to int& hence

the
> selection of foo(int const &) and that bar won't work.


Yes, I understand why the compiler gives the message. The message is
correct given the way the template parameters are deduced.

What I don't understand is why the language's template argument
deduction is so stupid that it selects a non-const type for a passed
rvalue when it's perfectly clear to the compiler that that's *never*
going to yield a valid call because the type is passed by reference.
AFAIK deduction of template arguments is a unification process between
the types of the passed values and the types listed in the function
prototype. But "int &" doesn't unify with "integer rvalue" *at all*!

When "10" is passed to a template function that has argument "T&" then
IMHO it should make T = "int const" and not "int", and if the standard
currently says otherwise then I sincerely hope that this will be
changed.

Anybody else have an opinion on this?

--Bart

 
Reply With Quote
 
Jean-Sebastien Samson
Guest
Posts: n/a
 
      04-22-2005
> When "10" is passed to a template function that has argument "T&" then
> IMHO it should make T = "int const" and not "int", and if the standard
> currently says otherwise then I sincerely hope that this will be
> changed.


It can't because 10 has type "int" not "const int". If however you had
something like

template <class T>
void bar_impl (T &) { /* do something */ }

template <class T>
void bar (T & t) { bar_impl(t); }

template <class T>
void bar (const T &) { bar_impl(t); }

Then bar(10) would work (and bar_impl<const int> would be called).
--
JS


 
Reply With Quote
 
Victor Bazarov
Guest
Posts: n/a
 
      04-22-2005
Jean-Sebastien Samson wrote:
>>When "10" is passed to a template function that has argument "T&" then
>>IMHO it should make T = "int const" and not "int", and if the standard
>>currently says otherwise then I sincerely hope that this will be
>>changed.

>
>
> It can't because 10 has type "int" not "const int". If however you had
> something like
>
> template <class T>
> void bar_impl (T &) { /* do something */ }
>
> template <class T>
> void bar (T & t) { bar_impl(t); }
>
> template <class T>
> void bar (const T &) { bar_impl(t); }


I am sure you meant

template <class T>
void bar (const T &t) { bar_impl(t); }

otherwise there's no 't'...

>
> Then bar(10) would work (and bar_impl<const int> would be called).


V
 
Reply With Quote
 
Jean-Sebastien Samson
Guest
Posts: n/a
 
      04-22-2005
>> template <class T>
>> void bar (const T &) { bar_impl(t); }

>
> I am sure you meant
>
> template <class T>
> void bar (const T &t) { bar_impl(t); }
>
> otherwise there's no 't'...


Yes. My apologies...
--
JS


 
Reply With Quote
 
Bart Samwel
Guest
Posts: n/a
 
      04-22-2005
>Then bar(10) would work (and bar_impl<const int> would be called).

I'd already thought about that, but that solution makes it extremely
difficult to "preserve" or "follow" the constness of a larger number of
parameters. I.e., if I want to do this for a bar_impl with ten
parameters, then I'll need 2^10=1024 overloads of "bar", one for every
possible combination of const/non-const parameters. For twenty
parameters this becomes 1024*1024, so this solution is infeasible. I've
thought about using a "wrapper class", but template argument deduction
only works when the types can match exactly (i.e. I can't let it deduce
"T = int" for a function argument of type "wrapper<T>" by passing "10"
and letting wrapper<int> have a constructor taking an integer). That
means I'm stuck with whatever I can deduce from the types that are
being passed, and there's apparently no efficient way of preserving
both the constness AND rvalueness AND referenceness of the type that is
being passed.

And the compiler _ knows_ that 10 is an rvalue and that it therefore
cannot be bound to a non-const reference. AFAICT it would be an
extremely simple addition to the standard to change this, so that it
deduces template arguments from rvalues as if they were "const". The
next question is why rvalues are non-const anyway, you can't modify
them but they aren't const, what is that about? If anybody in the know
would please enlighten me I'd be very grateful.

--Bart

 
Reply With Quote
 
Victor Bazarov
Guest
Posts: n/a
 
      04-22-2005
Bart Samwel wrote:
>[...] The
> next question is why rvalues are non-const anyway, you can't modify
> them but they aren't const, what is that about? If anybody in the know
> would please enlighten me I'd be very grateful.


Who said you can't modify rvalues?

#include <iostream>
struct A {
int a;
A(int a) : a(a) {}
void changeto(int a) { this->a = a; }
~A() { std::cout << "~A(): a = " << a << std::endl; }
};

A foo() {
return A(42);
}

int main() {
foo().changeto(20); // r-value is changed
}

What you can't modify is _literals_. rvalues (like in this example)
can designate objects, which of course are modifiable.

V
 
Reply With Quote
 
Jean-Sebastien Samson
Guest
Posts: n/a
 
      04-22-2005
> there's apparently no efficient way of preserving
> both the constness AND rvalueness AND referenceness of the type that is
> being passed.


I know that won't help you but why would you want that anyway ? I am
honestly curious what code you would apply to a constant litteral which
would require an argument of "T &" and not "const T &".
--
JS


 
Reply With Quote
 
Bart Samwel
Guest
Posts: n/a
 
      04-22-2005
Victor Bazarov wrote:
> What you can't modify is _literals_. rvalues (like in this example)
> can designate objects, which of course are modifiable.


Funny. If I do:

typedef int A;

A foo()
{
return A();
}

void bar(A&)
{
}

int main()
{
bar(foo());
}

Then my compiler tells me "A reference that is not to 'const' cannot be
bound to a non-lvalue", i.e. foo() is a non-lvalue. However, the
following compiles just fine:

class A
{
};

A foo()
{
return A();
}

void bar(A&)
{
}

int main()
{
bar(foo());
}

Which indicates that either my compiler's error message is wrong, or
foo() is not a non-lvalue (i.e., it is an lvalue) in this case, because
if foo() would have been an rvalue (and therefore a non-lvalue) then it
would not have been allowed to have been bound to a non-const
reference. I've checked the standard, and it says the compiler's error
message is indeed wrong (or at least incomplete)

[from basic.lval] An lvalue for an object is necessary in order to
modify the object except that an rvalue of class type can also be used
to modify its referent under certain circumstances. [Example: a member
function called for an object (class.mfct) can modify the object. ]

OK, so you're right about rvalues. So if I understand things correctly
we not only have the lvalue/rvalue distinction, but we also have
modifiable versus nonmodifiable rvalues. Sheesh! IIRC, when
nonmodifiable rvalues appear they cause havoc in other areas as well,
e.g. std::vector iterators implemented as simple pointers, where
(++somevector.begin()).foo() fails because somevector.begin() does not
yield a modifiable rvalue. I just hope there is a process going on
somewhere to fix all these places where nonmodifiable rvalues make
things "not work". Wouldn't you agree that there is something tobe
fixed here?

--Bart

 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Template argument deduction question Dilip C++ 7 11-16-2006 12:10 AM
template argument deduction ma740988 C++ 5 05-29-2006 04:10 PM
Template argument deduction cannot be performed George C++ 4 01-13-2006 12:15 AM
Template argument deduction Peng Yu C++ 1 04-16-2005 04:30 PM
Template argument deduction and conversion operators. BigMan@abv.bg C++ 3 02-02-2005 09:15 PM



Advertisments