0

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
      |           ~~~~~~~~~^
JeJo
  • 30,635
  • 6
  • 49
  • 88
Matt
  • 179
  • 6
  • 3
    Why do you need rvalue references? Usually there is no need to handle both non-const lvalue references and rvalue references. (Your example code might have been simplified, which is good, but at the moment, it shows only the need for const lvalue references.) – JaMiT Feb 26 '23 at 10:58
  • Why would universal references not be allowed? They were invented precisely to avoid the combinatorial explosion of ref, const ref, r-value cases in a single overload. – alfC Feb 26 '23 at 10:58
  • 1
    As JaMiT say, that's also true, if you are going to access a data member (.data) or many other regular operations you don't need the rvalue case in the first place. – alfC Feb 26 '23 at 11:00
  • 1
    You'll have to use a single template parameter per function parameter: `float bar(A&& a, B&& b, C&& c)`. And then, if you want to constrain the parameters to a specific family of types, you'll have to bolt on that constraint with `requires` or a form of SFINAE. – HolyBlackCat Feb 26 '23 at 11:06
  • @JaMiT using a const l-value reference instead would seem to be the solution! thank you – Matt Feb 26 '23 at 11:07
  • @HolyBlackCat thank you for providing a valid alternative – Matt Feb 26 '23 at 11:09

1 Answers1

2

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));
}
JeJo
  • 30,635
  • 6
  • 49
  • 88