473,386 Members | 1,997 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,386 software developers and data experts.

Template argument deduction on integer literals

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

Jul 23 '05 #1
14 2851
> 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
Jul 23 '05 #2
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

Jul 23 '05 #3
> 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
Jul 23 '05 #4
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
Jul 23 '05 #5
>> 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
Jul 23 '05 #6
>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

Jul 23 '05 #7
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
Jul 23 '05 #8
> 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
Jul 23 '05 #9
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

Jul 23 '05 #10
Bart Samwel wrote:
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:


It shouldn't for the same reason. What compiler are you using?
(BTW, Comeau online doesn't accept it)

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?


To be honest with you, I am used to work around language deficiencies
instead of asking so that they are fixed. Try posting your inquiry in
comp.std.c++, smart people hang out there.

V
Jul 23 '05 #11
I had *exact* same problem for two weeks, and found a nice solution.
Initially I thought to post a message here to get solution, but I
already did one, and
got nice answer from Victor Bazarov, who helped me to solve the first
problem. Thanks. After first problem, I ended-up with exact same problem
as yours, had to specialize 100s of templete functions with const, and
none const verions.
To keep this short, this is just a compiler option "-fno-const-strings".
I am using g++ 3.3.3. I'm sure other compilers must have something
similar.
But this is about char* vs const char* problem. In your case
it is int versus const int. Look into compiler options, and you may be
luckky to find one.

-haro

Bart Samwel wrote:
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

Jul 23 '05 #12
Jean-Sebastien Samson wrote:
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 &".


Well, the thing is, I want to basically have one function which
functions as kind of a "portal" for another function, and I don't know
anything about which function that is. Basically what we're doing is,
we're creating a generic strong reference / weak reference system, and
we are trying to _embed_ the refcounted object into a structure which
contains the reference counting information. The situation occurs when
you want to pass constructor parameters to the embedded object
unchanged. Look at this example (I haven't compiled this, it's just so
that you know what I'm talking about):

template<typename T>
struct Refcounted
{
int refcount;
/* some other info */
T the_object;

Refcounted() : T(), refcount(0) {}

template<typename T1>
Refcounted(T1& a1) : T(a1), refcount(0) {}

template<typename T1, typename T2>
Refcounted(T1& a1, T2& a2), refcount(0) {}

// ... and so on, up to 20 arguments
};

template<typename T>
class StrongRef
{
public:
StrongRef(Refcounted<T>& rcobj);
};

template<typename T>
Refcounted<T>
ref_new()
{
return StrongRef<T>(new Refcounted<T>());
}
template<typename T, typename T1>
Refcounted<T>
ref_new(T1& a1)
{
return StrongRef<T>(new Refcounted<T>(a1));
}
template<typename T, typename T1, typename T2>
StrongRef<T>
ref_new(T1& a1, T2& a2)
{
return StrongRef<T>(new Refcounted<T>(a1, a2));
}

// and so on, up to 20 arguments

// Usage examples:

struct B
{
};
struct C
{
};

struct A
{
A (C&);
A (const C&);
A(B);
A(int);
};

int main()
{
// Construct a strong reference to an A. This constructs the object
// of type A, as a member of a RefCounted<A> object, passing
// as constructor arguments a copy of b. In ref_new and in the
// RefCounted<A> constructor however, a *reference* to B is
// kept -- the copy is only created when the A object is
instantiated.
// This preserves exactly the semantics of a direct construction of
// A(b), i.e. the B object is only copied once.
B b;
StrongRef<A> aref1 = ref_new<A>( b );

// The same, but passing as constructor arguments a non-const
// reference to c. This works exactly the same as constructing
// an A(c) directly, i.e. there are no copies of c, the non-constness
// is preserved, so the constructor of A that is called is A(C&).
C c;
StrongRef<A> aref2 = ref_new<A>( c );

// The same, but passing an integer. The integer is passed as a
// non-const int reference to z by ref_new<A> and by the
// RefCounted<A> constructor, and A's constructor overload is
selected
// based on z's type, which is "int" and which is still preserved
// when the argument reaches RefCounted<A>'s constructor.
// Only there is the A(int) overload selected.
int z = 10;
StrongRef<A> aref3 = ref_new<A>( z );

// The same, but constructing with a literal. It is very common
// practice to construct objects with a literal, i.e. A(10), so
// we would like this to work. If this would call ref_new<A,const
// int>, then things would have been fine: this would then construct
// the A object as A(x) where x was a const reference to a temporary
// integer with value 10. However, it calls ref_new<A,int>, which
// fails because then it has to convert 10 into an int&, which is
// illegal.
StrongRef<A> aref4 = ref_new<A>( 10 ).
}

Things to note about this snippet:

1. The example constructors all have only one argument but they could
have had many more. We have no knowledge of the number of arguments
beforehand because it depends on the object type that ref_new is asked
to construct.

2. We don't actually know what the parameter types are that are passed
to ref_new<>, because the required arguments are dependent on both the
object type that we are trying to construct (the T parameter to
ref_new<T,...>) and the constructor overload of the constructed class
that the caller wishes to call. Basically, we have no clue if we're
going to be passed a nonmodifiable rvalue, a modifiable rvalue, or an
lvalue, but we want to handle all cases gracefully and pass everything
on as we got it. And right now, it works for _everything_ except for
nonmodifiable rvalues. :(

Does this explain why I'd want this? :)

--Bart

Jul 23 '05 #13
Victor Bazarov wrote:
It shouldn't for the same reason. What compiler are you using?
(BTW, Comeau online doesn't accept it)
This is MSVC++ 7.1. But AFAICT the standard only says that object-type
rvalues may be modifiable in some situations, it mentions method calls
explicitly, mentions no other situations but it does leave them open...
To be honest with you, I am used to work around language deficiencies
instead of asking so that they are fixed.
Well, I just moved from MSVC++ 6.0, so I'm more used to working around
compiler deficiencies than around language deficiencies. I'm only
getting to that point now that I have a compiler that is somewhere
close to standards-compliant. :/
Try posting your inquiry in comp.std.c++, smart people hang out

there.

Will do, thanks for the hint!

--Bart

Jul 23 '05 #14
Haro Panosyan wrote:
But this is about char* vs const char* problem. In your case
it is int versus const int. Look into compiler options, and you may be luckky to find one.


Thanks, but no such luck in MSVC++ 7.1. :(

--Bart

Jul 23 '05 #15

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

5
by: Suzanne Vogel | last post by:
Is it possible to store a pointer to a template function? eg, template<class T> fooFunc() {...} fptr = fooFunc; // <-- I couldn't find any info here, not even in the forums:...
4
by: Dave | last post by:
Hello all, Consider this template: template <typename T> void foo(T bar) {...} Here are three ways to instantiate this: 1.
2
by: marco | last post by:
the problem: I use a typedef inside a class template, than I use this type (dim_v<N1>::Type) to define the argument of a template function f but when I call this function from main, the compiler...
8
by: Thomas Heller | last post by:
I need to convert C preprocessor definitions into python code. The definitions are dumped out of gccxml (see http://www.gccxml.org) , running over the windows header files (for example). This...
3
by: BigMan | last post by:
Here is a piece of code: #include <memory> using namespace std; template< typename SomeType > void f(auto_ptr_ref< SomeType >) { }
1
by: Peng Yu | last post by:
Hi All, In the book Working Paper for Draft Proposed International Standard for Information Systems$)A!* Programming Language C+ +, template argument deduction is discussed. However, the...
4
by: George | last post by:
Dear All, I'm compiling the code below with IBM's xlC 6.0 and get the message, "rmspace.cpp", line 34.48: 1540-0298 (S) Template argument deduction cannot be performed using the function...
7
by: gretean | last post by:
I have a problem that's driving me crazy involving Microsoft's ability to deduce template parameters. I am using Visual Studio .NET (aka VC7?), and it gives an error compiling the following code....
3
by: Fei Liu | last post by:
Hello, We all know that a template function can automatically deduce its parameter type and instantiate, e.g. template <tpyename T> void func(T a); func(0.f); This will cause func<floatto...
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.