Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does it make any sense to specialize a function template with universal reference parameters?

For a template function, we can specialize it as follows:

template <typename T>
void func(T t) {}

// specialization for int
template <>
void func(int t) {}

However, I'm not sure how to specialize a template function with universal reference (a name from Meyers' book).

// template function with universal reference
template <typename T>
void func(T &&t) {}

I think simply replacing T with int doesn't make it a specialization:

template <>
void func(int &&t) {}

Since the template function can accept both lvalue and rvalue, while the 'specialized' one can only accept rvalue.

Should I also define a 'specialization' with lvalue reference?

// 'specialization' for lvalue reference
template <>
void func(int &t) {}

And the two 'specializations' make the specialization for the original template function? Or does it make any sense to have a specialization for it?

like image 783
for_stack Avatar asked Feb 16 '26 04:02

for_stack


2 Answers

Specializing function templates is rarely a good idea. Very rarely. It looks like a mixture of template class specialization and overloading, but works like neither.

Functions have overloads. Use those.

If overloading does not get the exact behaviour you want, use a helper function with tag dispatching based overloading, or forward to a class.

In this case, it could be as simple as:

template <typename T>
void func(T&&){}
void func(int){}

Or:

template<class T>struct tag_t{};
template<class T>constexpr tag_t<T>tag{};
namespace details{
  template <class T, class U>
  void func(tag_t<T>, U&&){}
  template <class U>
  void func(tag_t<int>, U&&){}
}
template <class T>
void func(T&&t){
  return details::func(tag<std::decay_t<T>>, std::forward<T>(t));
}
like image 129
Yakk - Adam Nevraumont Avatar answered Feb 17 '26 18:02

Yakk - Adam Nevraumont


When T is matched against a type it can be any of these (this is a non-exhaustive list):

int
int&
int const&
int&&
int volatile&
int volatile const&

... and so on.

Specialising in this case does not make much sense because we'd have to anticipate and write specialisations which match all use cases.

But maybe func actually represents a concept of some function which can be applied against a universal reference.

In which case what we'd probably do is something like this:

#include <iostream>

template<class Type>
struct func_op
{
  template<class T> void operator()(T&& t) const
  {
    // default implementation
  }
};

template <typename T>
void func(T&& t) 
{
  func_op<std::decay_t<T>> op;
  return op(std::forward<T>(t));
}


// now specialise the operation for all classes of int

template<>
struct func_op<int>
{
  template<class T> void operator()(T&& t) const
  {
    static_assert(std::is_same<std::decay_t<T>, int>(), 
                  "not an int!");
    std::cout << "some kind of operation on int" << t << std::endl;
  }
};



int main()
{
  int a = 5;
  const int b = 6;
  const volatile int c = 7;
  volatile int d = 8;
  func(a);
  func(b);
  func(c);
  func(d);
  func(std::move(a));
  func(std::move(b));
  func(std::move(c));
  func(std::move(d));

}

Here, the Type template argument in the specialisation of func_op<> represents the general value type. We then provide a templated operator() in order to provide the universal reference-based implementation.

The T in func<T> is converted into a 'general value type' by std::decay_t - which has the effect of stripping off all const, volatile and reference modifiers and leaving us with a raw type (e.g. int).

We could further specialise or overload operator() if we wished to provide special handling for (for example) const int refs.

like image 21
Richard Hodges Avatar answered Feb 17 '26 18:02

Richard Hodges



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!