16

Reduced sample code:

#include <iostream>

template<typename T>
void func(T &x)
{
    std::cout << "non-const " << x << std::endl;
}

template<typename T>
void func(const T &x)
{
    std::cout << "const " << x << std::endl;
}

template<typename ...ARGS>
void proxy(ARGS ...args)
{
    func(args...);
}

int main()
{
    int i = 3;

    func(i);
    func(5);
    func("blah");

    proxy(i);
    proxy(5);
    proxy("blah");
}

Expected output:

non-const 3
const 5
const blah
non-const 3
const 5
const blah

Actual output:

non-const 3
const 5
const blah
non-const 3
non-const 5
non-const blah

So somehow the const qualifier of the function parameter gets lost when put through the variadic template. Why? How can I prevent this?

PS: tested with GCC 4.5.1 and SUSE 11.4

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user1346466
  • 630
  • 4
  • 11
  • It has nothing to do with variadic templates. Your function template proxy's parameters are non-references, so the const of the function arguments is ignored when deducing the template arguments. – Cosyn Apr 20 '12 at 13:31

2 Answers2

20

You just stumble upon the forwarding problem. This issue is solved using perfect forwarding.

Basically, you need to take your parameters by rvalue-reference, and rely on std::forward to correctly forward them while keeping their nature:

template<typename ...Args>
void proxy(Args&& ...args)  
{
    func(std::forward<Args>(args)...);
}
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
Luc Touraille
  • 79,925
  • 15
  • 92
  • 137
6

As Luc already mentioned this is a problem of forwarding and the answer to how to prevent it is to use perfect forwarding. But I will try to address the other questions at the end:

So somehow the const qualifier of the function parameter gets lost when put through the variadic template. Why?

This has everything to do with type inference. Ignore that you are using variadic templates and consider the simplest one argument template:

template <typename T>
void one_arg_proxy( T arg ) {
   func( arg );
}

At the place of call you have one_arg_proxy( 5 ), that is, the argument is an int rvalue. Type inference kicks in to figure out what the type T should be, and the rules dictate that T is int, so the call gets translated to one_arg_proxy<int>(5) and the instantiation of the template that gets compiled is:

template <>
void one_arg_proxy<int>( int arg ) {
   func( arg );
}

Now the call to func takes an lvalue argument, and thus the version of func taking a non-const reference is a better match (no conversions required) than the one taking a const&, yielding the result that you are getting. The issue here is that func does not get called with the argument to proxy, but rather with the internal copy that proxy made of it.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489