Template argument deduction on integer literals

Discussion in 'C++' started by Bart Samwel, Apr 22, 2005.

  1. Bart Samwel

    Bart Samwel Guest

    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
     
    Bart Samwel, Apr 22, 2005
    #1
    1. Advertising

  2. > 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
     
    Jean-Sebastien Samson, Apr 22, 2005
    #2
    1. Advertising

  3. Bart Samwel

    Bart Samwel Guest

    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
     
    Bart Samwel, Apr 22, 2005
    #3
  4. > 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
     
    Jean-Sebastien Samson, Apr 22, 2005
    #4
  5. 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
     
    Victor Bazarov, Apr 22, 2005
    #5
  6. >> 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
     
    Jean-Sebastien Samson, Apr 22, 2005
    #6
  7. Bart Samwel

    Bart Samwel Guest

    >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
     
    Bart Samwel, Apr 22, 2005
    #7
  8. 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
     
    Victor Bazarov, Apr 22, 2005
    #8
  9. > 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
     
    Jean-Sebastien Samson, Apr 22, 2005
    #9
  10. Bart Samwel

    Bart Samwel Guest

    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
     
    Bart Samwel, Apr 22, 2005
    #10
  11. 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
     
    Victor Bazarov, Apr 22, 2005
    #11
  12. 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
    >
     
    Haro Panosyan, Apr 22, 2005
    #12
  13. Bart Samwel

    Bart Samwel Guest

    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
     
    Bart Samwel, Apr 22, 2005
    #13
  14. Bart Samwel

    Bart Samwel Guest

    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
     
    Bart Samwel, Apr 22, 2005
    #14
  15. Bart Samwel

    Bart Samwel Guest

    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
     
    Bart Samwel, Apr 22, 2005
    #15
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    3
    Views:
    4,348
  2. Peng Yu

    Template argument deduction

    Peng Yu, Apr 16, 2005, in forum: C++
    Replies:
    1
    Views:
    509
    Mike Wahler
    Apr 16, 2005
  3. George
    Replies:
    4
    Views:
    411
    George
    Jan 13, 2006
  4. ma740988

    template argument deduction

    ma740988, May 29, 2006, in forum: C++
    Replies:
    5
    Views:
    1,457
    Bo Persson
    May 29, 2006
  5. Dilip
    Replies:
    7
    Views:
    385
    Andrey Tarasevich
    Nov 16, 2006
Loading...

Share This Page