2

Suppose I have a template function foo() that takes two integer references as parameters. I'd like the template function to also automatically handle constant references (such as those from constants). Here is a generalized example. I can get foo() to work, but I have to provide a new implementation for every permutation of reference/const-reference parameters.

#include <iostream>

using namespace std;

template<typename A, typename B>
void foo(A& a, B& b)
{
    cout<<"a:"<<a<<" b:"<<b<<endl;
}

template<typename A, typename B>
void foo(A& a, const B& b)
{
    cout<<"a:"<<a<<" b:"<<b<<endl;
}

template<typename A, typename B>
void foo(const A& a, B& b)
{
    cout<<"a:"<<a<<" b:"<<b<<endl;
}

template<typename A, typename B>
void foo(const A& a, const B& b)
{
    cout<<"a:"<<a<<" b:"<<b<<endl;
}

int main()
{
    int x = 0;

    foo(x, x);
    foo(x, 10);
    foo(10, x);
    foo(20, 20);

    return 0;
}

The above example is a little bit contrived, but it is a generalization of what I am trying to do. In my more complex case, I have a class that acts as a wrapper to a set of parameters. The class constructor is templated, like foo(), and can have as many as 10 parameters. It would be a nightmare to enumerate all 2^10 possible constructors.

Glenn
  • 386
  • 3
  • 12
  • I'm self-admitted to not be an expert, but this question sounds eerily related to perfect forwarding (the permutations were a not-so-subtle hint in that direction). One pretty solid discussion is in the lead-answer for [this question](http://stackoverflow.com/questions/3582001/advantages-of-using-forward) and may well be worth a look on your part. – WhozCraig Aug 03 '13 at 04:01

3 Answers3

4

The problem that you describe is perfect forwarding problem. C++11 solved this problem with universal references:

template<typename A, typename B>
void foo(A&& a, B&& b) {
    bar(std::forward<A>(a), std::forward<B>(b));
}

Parameters here are not rvalue references, but universal references. They will have the same ref-ness and const-ness as arguments.

If arguments are rvalues, in foo parameters will be rvalues with names. Named rvalues are lvalues. To pass parameters to sub-functions with preserved value-ness, you need to wrap them in std::forward. Function bar will get a and b with exactly the same type as foo.

Leonid Volnitsky
  • 8,854
  • 5
  • 38
  • 53
1

If the template is not going to modify the arguments, then just offer the version with the const& and you should be fine:

template<typename A, typename B>
void foo(const A& a, const B& b)
{
    cout<<"a:"<<a<<" b:"<<b<<endl;
}

If you pass a non-const lvalue, it will still be bound by a const reference and everything will work.

If you want some of the overloads to modify the arguments, then rethink the design, as those don't seem like functions that should share a name. There are exceptions, for examples, accessors into internal members of a structure, where you might want to return a const& if the object is const or a non-const reference otherwise... If that is the case, you can go the opposite way and offer only the non-const overload:

template<typename A, typename B>
void foo(A& a, B& b)

In this case, if the argument is a temporary or a non-const reference, the deduce type will reflect it and it will bind the argument with a const&.

int main() {
   int a = 5;
   const int b = 10;
   foo(a,b);          // foo<int,const int>(int&,const int&)
   foo(10,b);         // foo<const int,const int>(const int&, const int&)
}

Rereading your question it seems that you might be interested in perfect forwarding (this might or not fit your bill). If that is the case, and if you have a C++11 compiler you can use universal-references with a variadic template. Building a good wrapper is a hard thing, although you might be able to just use std::tuple as the actual storage, which should make the task quite simple.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • I decided to go with making everything const... and then casting it away. The code compiles. It's not the safest approach. It would never work in general. However, in this specific case, it's safe because there are other safe-guards in place to prevent a stack temporary from going out of scope too soon. I appreciate the C++11 suggestions. Perfect forwarding sounds like it could do the trick. We hope to begin supporting it in our project in a year or two (waiting for 3rd-party updates). – Glenn Aug 05 '13 at 05:14
0

Apart from all the answers posted which are correct, here is a simple program just for your reference as this might help you to understand the concept better.

#include<iostream>
template<class X>
void func(X& x, X& y)
{
    //your code
}
template<class X, class Y>
void intermediateFunc(X&& x, Y&& y)
{
    func(x,y);
}

int main()
{
    int y = 9;
    intermediateFunc(5,5);
    intermediateFunc(y,5);
    intermediateFunc(5,y);
    intermediateFunc(y,y);
}
Saksham
  • 9,037
  • 7
  • 45
  • 73