C++ template function and universal reference error?

85 Views Asked by At

What is the exact difference of universal reference and rvalue reference?

The code below should except both rvalue reference and lvalue reference as argument. And apparently the first function for_each takes both successfully. However, when it calls the internal function min_iterable_length, it does not work as expected. It seems that the internal function is expecting only a rvalue reference not lvalue reference.

What is the difference? Apparently the first function for_each did take lvalue reference as legal argument. And I used std::forward to pass it exactly to the second function. And why is the second function only accept rvalue reference?

#include <array>
#include <vector>
#include <utility>    

template <typename A, size_t N>
size_t min_iterable_length(A(&&)[N])
{
    return N;
}

template <typename A, size_t N>
size_t min_iterable_length(std::array<A, N> &&)
{
    return N;
}

template <typename A>
size_t min_iterable_length(std::vector<A> &&vector)
{
    return vector.size();
}

template <typename A, size_t N, typename... Args>
size_t min_iterable_length(A (&&head)[N], Args &&...tail)
{
    size_t n_t = min_iterable_length(std::forward<Args>(tail)...);
    return N < n_t ? N : n_t;
}

template <typename A, size_t N, typename... Args>
size_t min_iterable_length(std::array<A, N> &&head, Args &&...tail)
{
    size_t n_t = min_iterable_length(std::forward<Args>(tail)...);
    return N < n_t ? N : n_t;
}

template <typename A, typename... Args>
size_t min_iterable_length(std::vector<A> &&head, Args &&...tail)
{
    size_t n_h = head.size();
    size_t n_t = min_iterable_length(std::forward<Args>(tail)...);
    return n_h < n_t ? n_h : n_t;
}

template <typename F, typename... Args>
void for_each(F f, Args &&...args)
{
    size_t length = min_iterable_length(std::forward<Args>(args)...);

    for (int i = 0; i < length; ++i)
    {
        f(args[i]...);
    }

    return;
}

int c_array_3[3] = {1, 2, 3};
int res_1 = 0;
auto add_1 = [&](int x)
{
    res_1 += x;
};

for_each(add_1, c_array_3);
[build] /Users/username/Documents/dev/arduino/libraries/efp/test/prelude_test.cpp:32:5: note: in instantiation of function template specialization 'for_each<(lambda at /Users/username/Documents/dev/arduino/libraries/efp/test/prelude_test.cpp:27:18), std::array<double, 3> &>' requested here
[build]     for_each(add_2, std_array_3);
[build]     ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:22:8: note: candidate function [with A = double, N = 3] not viable: expects an rvalue for 1st argument
[build] size_t min_iterable_length(std::array<A, N> &&)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:41:8: note: candidate function [with A = double, N = 3, Args = <>] not viable: expects an rvalue for 1st argument
[build] size_t min_iterable_length(std::array<A, N> &&head, Args &&...tail)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:16:8: note: candidate template ignored: could not match 'A[N]' against 'std::array<double, 3>'
[build] size_t min_iterable_length(A(&)[N])
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:28:8: note: candidate template ignored: could not match 'vector' against 'array'
[build] size_t min_iterable_length(std::vector<A> &&vector)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:34:8: note: candidate template ignored: could not match 'A[N]' against 'std::array<double, 3>'
[build] size_t min_iterable_length(A (&head)[N], Args &&...tail)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:48:8: note: candidate template ignored: could not match 'vector' against 'array'
[build] size_t min_iterable_length(std::vector<A> &&head, Args &&...tail)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:58:21: error: no matching function for call to 'min_iterable_length'
[build]     size_t length = min_iterable_length(std::forward<Args>(args)...);
[build]                     ^~~~~~~~~~~~~~~~~~~
[build] /Users/username/Documents/dev/arduino/libraries/efp/test/prelude_test.cpp:41:5: note: in instantiation of function template specialization 'for_each<(lambda at /Users/username/Documents/dev/arduino/libraries/efp/test/prelude_test.cpp:36:18), std::vector<double> &>' requested here
[build]     for_each(add_3, std_vector_3);
[build]     ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:28:8: note: candidate function [with A = double] not viable: expects an rvalue for 1st argument
[build] size_t min_iterable_length(std::vector<A> &&vector)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:48:8: note: candidate function [with A = double, Args = <>] not viable: expects an rvalue for 1st argument
[build] size_t min_iterable_length(std::vector<A> &&head, Args &&...tail)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:16:8: note: candidate template ignored: could not match 'A[N]' against 'std::vector<double>'
[build] size_t min_iterable_length(A(&)[N])
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:22:8: note: candidate template ignored: could not match 'array' against 'vector'
[build] size_t min_iterable_length(std::array<A, N> &&)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:34:8: note: candidate template ignored: could not match 'A[N]' against 'std::vector<double>'
[build] size_t min_iterable_length(A (&head)[N], Args &&...tail)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:41:8: note: candidate template ignored: could not match 'array' against 'vector'
[build] size_t min_iterable_length(std::array<A, N> &&head, Args &&...tail)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:58:21: error: no matching function for call to 'min_iterable_length'
[build]     size_t length = min_iterable_length(std::forward<Args>(args)...);
[build]                     ^~~~~~~~~~~~~~~~~~~
[build] /Users/username/Documents/dev/arduino/libraries/efp/test/prelude_test.cpp:50:5: note: in instantiation of function template specialization 'for_each<(lambda at /Users/username/Documents/dev/arduino/libraries/efp/test/prelude_test.cpp:45:24), std::vector<double> &, int (&)[3]>' requested here
[build]     for_each(add_product, std_vector_3, c_array_3);
[build]     ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:48:8: note: candidate function [with A = double, Args = <int (&)[3]>] not viable: expects an rvalue for 1st argument
[build] size_t min_iterable_length(std::vector<A> &&head, Args &&...tail)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:34:8: note: candidate template ignored: could not match 'A[N]' against 'std::vector<double>'
[build] size_t min_iterable_length(A (&head)[N], Args &&...tail)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:41:8: note: candidate template ignored: could not match 'array' against 'vector'
[build] size_t min_iterable_length(std::array<A, N> &&head, Args &&...tail)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:28:8: note: candidate function template not viable: requires single argument 'vector', but 2 arguments were provided
[build] size_t min_iterable_length(std::vector<A> &&vector)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:22:8: note: candidate function template not viable: requires 1 argument, but 2 were provided
[build] size_t min_iterable_length(std::array<A, N> &&)
[build]        ^
[build] /Users/username/Documents/dev/arduino/libraries/efp/./src/prelude.hpp:16:8: note: candidate function template not viable: requires 1 argument, but 2 were provided
[build] size_t min_iterable_length(A(&)[N])
1

There are 1 best solutions below

0
maxplus On

Neither of the min_iterable_length functions are declared to accept forwarding reference for their first argument, they all accept their first argument as an rvalue reference.

Forwarding references are either:

  1. function parameter of a function template declared as rvalue reference to cv-unqualified type template parameter of that same function template
  2. auto&& except when deduced from a brace-enclosed initializer list

Your code can be corrected for example like this:

template <typename A>
size_t min_iterable_length(A&& a) {
  return std::size(a);
}

template <typename A, typename... Args>
size_t min_iterable_length(A&& head, Args&&... tail) {
  size_t n_t = min_iterable_length(std::forward<Args>(tail)...);
  return std::min(std::size(head), n_t);
}

template <typename F, typename... Args>
void for_each(F f, Args &&...args) {
  size_t length = min_iterable_length(std::forward<Args>(args)...);
  for (int i = 0; i < length; ++i) {
    f(args[i]...);
  }
}

or even:

template <typename A>
size_t min_iterable_length(const A& a) {
  return std::size(a);
}

template <typename A, typename... Args>
size_t min_iterable_length(const A& head, const Args&... tail) {
  size_t n_t = min_iterable_length(tail...);
  return std::min(std::size(head), n_t);
}

template <typename F, typename... Args>
void for_each(F f, Args &&...args) {
  size_t length = min_iterable_length(args...);
  for (int i = 0; i < length; ++i) {
    f(args[i]...);
  }
}

since you don't need forwarding inside min_iterable_length and it's perfectly fine to bind an lvalue reference to a function argument declared as rvalue or forwarding reference.

For conciseness, min_iterable_length can be removed altogether:

template <typename F, typename... Args>
void for_each(F f, Args&&... args) {
  size_t length = std::min({std::size(args)...});
  for (int i = 0; i < length; ++i) {
    f(args[i]...);
  }
}