Search code examples
c++c++11perfect-forwardingstdtuple

perfect forwarding failing for lvalues


I have a utility function which iterates over a tuple, and for each element, calls a function with that element, and finally calls another function with the result of all the tuple elements.

To better illustrate:

  • There is a tuple of various types tuple<Foo<int>, Bar<double>, Baz<char>>
  • Each type Foo, Bar and Baz have an implicit interface ClassT::data() which returns a reference to some internal member T&.
  • You have a function with the signature void (int, double, char)

The utility iterates over the tuple members, extracting the reference to the internal members, and calls the function with the required parameters.

Issue: perfect forwarding

I have tried to implement the utility using so-called "universal references" and perfect forwarding, thereby negating the need for multiple lvalue/rvalue and const/non-const overloads.

Whilst the parameter to the function is of type

template<typename Tuple>
auto invoke(Tuple&& tuple)

I cannot bind lvalues to it. Why is this?

I have asked a similar question leading up to this here which has been solved; however since this is an unrelated issue, I believe this warrants a new question

Example on ideone: https://ideone.com/lO5JOB

#include <tuple>
#include <iostream>

// sequence

template<size_t...>
struct Sequence
{ };

template<size_t N, size_t... Seq>
struct GenerateSequence : GenerateSequence<N - 1, N - 1, Seq...>
{ };

template<size_t... Seq>
struct GenerateSequence<0, Seq...>
{
    using type = Sequence<Seq...>;
};

// invoke tuple

struct TupleForEachInvoker
{
    template<typename Func, typename ForEachFunc, typename Tuple, size_t... Seq>
    static auto invoke(Func&& func, ForEachFunc&& forEachFunc, Tuple&& tuple, Sequence<Seq...>)
        -> decltype(func(forEachFunc(std::get<Seq>(std::forward<Tuple>(tuple)))...))
    {
        return func(forEachFunc(std::get<Seq>(std::forward<Tuple>(tuple)))...);
    }

    template<typename Func, typename ForEachFunc, typename... Args>
    static auto apply(Func&& func, ForEachFunc&& forEachFunc, std::tuple<Args...>&& args)
        -> decltype(invoke(std::forward<Func>(func),
                           std::forward<ForEachFunc>(forEachFunc),
                           std::forward<std::tuple<Args...>>(args),
                           typename GenerateSequence<sizeof...(Args)>::type()))
    {
        return invoke(std::forward<Func>(func),
                      std::forward<ForEachFunc>(forEachFunc),
                      std::forward<std::tuple<Args...>>(args),
                      typename GenerateSequence<sizeof...(Args)>::type());
    }
};

template<typename Func, typename ForEachFunc, typename Tuple>
inline auto invokeWithMemberFromAll(Func&& func, ForEachFunc&& forEachFunc, Tuple&& tuple)
    -> decltype(TupleForEachInvoker::apply(std::forward<Func>(func),
                                           std::forward<ForEachFunc>(forEachFunc),
                                           std::forward<Tuple>(tuple)))
{
    return TupleForEachInvoker::apply(std::forward<Func>(func),
                                      std::forward<ForEachFunc>(forEachFunc),
                                      std::forward<Tuple>(tuple));
}

// exemplar

template<typename T>
struct Foo
{
    T& data() { return _val; }
    T _val;
};

struct Extract
{
    template<typename T>
    T& operator() (Foo<T>& f) { return f.data(); }
};

int main()
{
    Foo<int>         i { 5 };
    Foo<double>      d { 6. };
    Foo<const char*> s { "hello world" };

    auto cb = [](int& i, const double& d, const char* s)
        {
            std::cout << "i=" << i << ", d=" << d << ", s=" << s << std::endl;

            i += 2;
        };


    // rvalue reference to tuple
    invokeWithMemberFromAll(cb, Extract{}, std::tie(i, d, s));

    std::cout << i.data() << std::endl;

    // lvalue reference to tuple - fails
    auto tuple = std::tie(i, d, s);
    invokeWithMemberFromAll(cb, Extract{}, tuple);

    std::cout << i.data() << std::endl;

}

Solution

  • template<typename Func, typename ForEachFunc, typename... Args>
    static auto apply(Func&& func, ForEachFunc&& forEachFunc, std::tuple<Args...>&& args)
    

    the 3rd argument is an rvalue tuple, not a tuple of forwarding references, or a forwarding reference to a tuple.

    Universal references (currently called "forwarding references" in the under development C++1z standard) usually require a deduced type. They do require that the type the && is applied to could be either a reference or a non-reference type. A std::tuple<?> is always a non-reference type, so && makes it an rvalue reference, not a forwarding reference, when placed like std::tuple<?>&&.

    It appears you switch from Tuple&& to std::tuple<?>&& for no reason other than to count the number of Args.... If I'm right, then std::tuple_size<std::decay_t<Tuple>>{} equals sizeof...(Args).