Search code examples
c++templatesfunction-templatesclass-templateforwarding-reference

How can I avoid writing every permutation of L and R value references for a function which does not allow universal references?


This example code below is designed to express the need to achieve 2 goals on multiple instances of other code:

  1. Maintain full type specification in template function bar.
  2. Avoid writing all permutations of l-value and r-value references for bar.

As the number of parameters in the function bar grows, it becomes more difficult to define every permutation of l-value and r-value references.

Currently, I am not able to use r-value references in bar function.

Is there any method, that I'm not aware of? Or should I write every permutation as a last resort?

Godbolt

#include <iostream>
#include <vector>
// Skip down to the bar function

template<typename DT> struct X
{
    DT data;

    X(DT data) : data(data * 2) {}
    template<typename VT>
    float foo(VT value) { return data + value; }
};

template<typename DT> struct Y 
{
    DT data;

    Y(DT data) : data(data * 4) {}
    template<typename VT>
    float foo(VT value) { return value * data; }
};

template<typename DT> struct Z
{
    DT data;

    Z(DT data) : data(data * 8) {}
    template<typename VT>
    float foo(VT value) { return (value + 1) * (data + 1); }
};

// Function of interest
template <template<typename> typename XT, typename AT,
    template<typename> typename YT, typename BT,
    template<typename> typename ZT, typename CT>
float bar(XT<AT>&& a, YT<BT>&& b, ZT<CT>&& c) // Universal references are not allowed
{ 
    XT<float> a_result(b.data - c.data);
    YT<float> b_result(a.data - c.data);
    XT<float> c_result(a.data + b.data);

    return a_result.foo(a.data) + b_result.foo(b.data) + c_result.foo(c.data);
}

int main() 
{
    X<float> x(1.);
    Y<double> y(2.);
    Z<int> z(3);

    double result = bar(x, y, z);
    std::cout << result << std::endl;
}

The Godbolt output for the code above

Could not execute the program
Compiler returned: 1
Compiler stderr
<source>: In function 'int main()':
<source>:52:23: error: cannot bind rvalue reference of type 'X<float>&&' to lvalue of type 'X<float>'
   52 |   double result = bar(x, y, z);
      |                       ^
<source>:39:20: note:   initializing argument 1 of 'float bar(XT<AT>&&, YT<BT>&&, ZT<CT>&&) [with XT = X; AT = float; YT = Y; BT = double; ZT = Z; CT = int]'
   39 | float bar(XT<AT>&& a, YT<BT>&& b, ZT<CT>&& c) { // Universal references not allowed
      |           ~~~~~~~~~^

Solution

  • In your shown simplified code, it seems like you need only const qualified l-value reference.

    However, if you insist having the forwarding ref (àka. universal reference) been used instead, I recommend changing the template template parameters to be a simple template function as follows:

    template <typename XT, typename YT, typename ZT>
    auto bar(XT&& a, YT&& b, ZT&& c)
    // uncomment the following if you want to restrict
    // the rvalue references parameters been used (aka. SFINAE)
    // -> std::enable_if_t<       
    //     !std::is_rvalue_reference_v<decltype(a)> && 
    //     !std::is_rvalue_reference_v<decltype(b)> &&
    //     !std::is_rvalue_reference_v<decltype(c)>
    //     , float>
    {
        // type alias for common type
        using CommonType = std::common_type_t<decltype(a.data), decltype(b.data), decltype(c.data)>;
    
         X<CommonType> a_result(b.data - c.data);
         Y<CommonType> b_result(a.data - c.data);
         Z<CommonType> c_result(a.data + b.data);
        return static_cast<float>(a_result.foo(a.data) + b_result.foo(b.data) + c_result.foo(c.data));
    }