Decltype for the return type of a recursive variational function template

Given the following code (taken from here ):

#include <cstddef>
#include <type_traits>
#include <tuple>
#include <iostream>
#include <utility>
#include <functional>

template<typename ... Fs>
struct compose_impl
{
    compose_impl(Fs&& ... fs) : functionTuple(std::forward_as_tuple(fs ...)) {}

    template<size_t N, typename ... Ts>
    auto apply(std::integral_constant<size_t, N>, Ts&& ... ts) const
    {
        return apply(std::integral_constant<size_t, N - 1>(), std::get<N>  (functionTuple)(std::forward<Ts>(ts)...));
    }

    template<typename ... Ts>
    auto apply(std::integral_constant<size_t, 0>, Ts&& ... ts) const
    {
        return std::get<0>(functionTuple)(std::forward<Ts>(ts)...);
    }

    template<typename ... Ts>
    auto operator()(Ts&& ... ts) const
    {
         return apply(std::integral_constant<size_t, sizeof ... (Fs) - 1>(), std::forward<Ts>(ts)...);
    }

    std::tuple<Fs ...> functionTuple;
};

template<typename ... Fs>
auto compose(Fs&& ... fs)
{
     return compose_impl<Fs ...>(std::forward<Fs>(fs) ...);
}

int main ()
{
    auto f1 = [](std::pair<double,double> p) {return p.first + p.second;    };
    auto f2 = [](double x) {return std::make_pair(x, x + 1.0); };
    auto f3 = [](double x, double y) {return x*y; };
    auto g = compose(f1, f2, f3);

    std::cout << g(2.0, 3.0) << std::endl;   //prints '13', evaluated as (2*3) + ((2*3)+1)
    return 0;
}

The code above works in C ++ 14. I had problems with its work in C ++ 11. I tried to correctly provide return types for the function templates involved, but without much success, for example:

template<typename... Fs>
struct compose_impl
{
    compose_impl(Fs&&... fs) : func_tup(std::forward_as_tuple(fs...)) {}

    template<size_t N, typename... Ts>
    auto apply(std::integral_constant<size_t, N>, Ts&&... ts) const -> decltype(std::declval<typename std::tuple_element<N, std::tuple<Fs...>>::type>()(std::forward<Ts>(ts)...))
    // -- option 2. decltype(apply(std::integral_constant<size_t, N - 1>(), std::declval<typename std::tuple_element<N, std::tuple<Fs...>>::type>()(std::forward<Ts>(ts)...)))
    {
         return apply(std::integral_constant<size_t, N - 1>(), std::get<N>(func_tup)(std::forward<Ts>(ts)...));
    }

    using func_type = typename std::tuple_element<0, std::tuple<Fs...>>::type;
    template<typename... Ts>
    auto apply(std::integral_constant<size_t, 0>, Ts&&... ts) const -> decltype(std::declval<func_type>()(std::forward<Ts>(ts)...))
    {
        return std::get<0>(func_tup)(std::forward<Ts>(ts)...);
    }

    template<typename... Ts>
    auto operator()(Ts&&... ts) const -> decltype(std::declval<func_type>()(std::forward<Ts>(ts)...))
    // -- option 2. decltype(apply(std::integral_constant<size_t, sizeof...(Fs) - 1>(), std::forward<Ts>(ts)...))
    {
        return apply(std::integral_constant<size_t, sizeof...(Fs) - 1>(), std::forward<Ts>(ts)...);
    }

    std::tuple<Fs...> func_tup;
};

template<typename... Fs>
auto compose(Fs&&... fs) -> decltype(compose_impl<Fs...>(std::forward<Fs>(fs)...))
{
   return compose_impl<Fs...>(std::forward<Fs>(fs)...);
}

For the above clang (3.5.0), the following error occurs:

func_compose.cpp:79:18: error: no matching function for call to object of type 'compose_impl<(lambda at func_compose.cpp:65:15) &, (lambda at func_compose.cpp:67:15) &,
  (lambda at func_compose.cpp:68:15) &>'
std::cout << g(2.0, 3.0) << std::endl;   //prints '13', evaluated as (2*3) + ((2*3)+1)
             ^
 func_compose.cpp:31:10: note: candidate template ignored: substitution failure [with Ts = <double, double>]: no matching function for call to object of type
  '(lambda at func_compose.cpp:65:15)'
 auto operator()(Ts&&... ts) /*const*/ -> decltype(std::declval<func_type>()(std::forward<Ts>(ts)...))
     ^                                            ~~~
1 error generated.

If I try "option 2." I get almost the same error.

Besides the fact that it looks very verbose, I also cannot understand that it is correct. Can someone give an idea of ​​what I am doing wrong? Is there an easier way to provide return types?

+4
1

,

std::declval<func_type>()(std::forward<Ts>(ts)...)

f1 double (, operator()), std::pair (func_type ).

2 , , , , , , decltype(apply(...)) apply.


, , , , , .

, , : apply operator() compose_impl - f1 .

, :

#include <cstddef>
#include <type_traits>
#include <tuple>
#include <iostream>
#include <utility>
#include <functional>

template<typename> struct ret_hlp;

template<typename F, typename R, typename... Args> struct ret_hlp<R (F::*)(Args...) const>
{
    using type = R;
};

template<typename F, typename R, typename... Args> struct ret_hlp<R (F::*)(Args...)>
{
    using type = R;
};

template<typename ... Fs>
struct compose_impl
{
    compose_impl(Fs&& ... fs) : functionTuple(std::forward_as_tuple(fs ...)) {}

    using f1_type = typename std::remove_reference<typename std::tuple_element<0, std::tuple<Fs...>>::type>::type;
    using ret_type = typename ret_hlp<decltype(&f1_type::operator())>::type;

    template<size_t N, typename ... Ts>
    ret_type apply(std::integral_constant<size_t, N>, Ts&& ... ts) const
    {
        return apply(std::integral_constant<size_t, N - 1>(), std::get<N>  (functionTuple)(std::forward<Ts>(ts)...));
    }

    template<typename ... Ts>
    ret_type apply(std::integral_constant<size_t, 0>, Ts&& ... ts) const
    {
        return std::get<0>(functionTuple)(std::forward<Ts>(ts)...);
    }

    template<typename ... Ts>
    ret_type operator()(Ts&& ... ts) const
    {
         return apply(std::integral_constant<size_t, sizeof ... (Fs) - 1>(), std::forward<Ts>(ts)...);
    }

    std::tuple<Fs ...> functionTuple;
};

template<typename ... Fs>
compose_impl<Fs ...> compose(Fs&& ... fs)
{
     return compose_impl<Fs ...>(std::forward<Fs>(fs) ...);
}

int main ()
{
    auto f1 = [](std::pair<double,double> p) {return p.first + p.second;    };
    auto f2 = [](double x) {return std::make_pair(x, x + 1.0); };
    auto f3 = [](double x, double y) {return x*y; };
    auto g = compose(f1, f2, f3);

    std::cout << g(2.0, 3.0) << std::endl;   //prints '13', evaluated as (2*3) + ((2*3)+1)
    return 0;
}

:

  • GCC 4.9.1 Clang 3.5.0 ++ 11 Visual ++ 2013.
  • , ret_hlp , operator() -, , .
  • ; , , : compose lvalue ( ), functionTuple compose_impl . , , , .

EDIT: , :

, - Fs&& ... compose. F&&, ( ), A, :

  • rvalue, F A, A&& (, , compose);
  • lvalue, F A&, A& &&, A& ( , , f1, - lvalues).

, compose_impl - ( )

compose_impl<lambda_1_type&, lambda_2_type&, lambda_3_type&>

, , functionTuple

std::tuple<lambda_1_type&, lambda_2_type&, lambda_3_type&>

- compose, , , functionTuple

std::tuple<lambda_1_type, lambda_2_type, lambda_3_type>

, , .

, , ; , .

, ( ), . - std::decay, , - , , compose_impl, .

- functionTuple, , :

std::tuple<typename std::decay<Fs>::type ...> functionTuple;

, , , .

, ; , "": -).


EDIT 2 OP: , , std::decay ( ret_type , ) , :

int f(int) { return 7; }

int main()
{
    auto c1 = compose(&f, &f); //Stores pointers to function f.
    auto c2 = compose(f, f); //Stores references to function f.
    auto pf = f; //pf has type int(*)(int), but is an lvalue, as opposed to &f, which is an rvalue.
    auto c3 = compose(pf, pf); //Stores references to pointer pf.
    std::cout << std::is_same<decltype(c1.functionTuple), std::tuple<int(*)(int), int(*)(int)>>::value << '\n';
    std::cout << std::is_same<decltype(c2.functionTuple), std::tuple<int(&)(int), int(&)(int)>>::value << '\n';
    std::cout << std::is_same<decltype(c3.functionTuple), std::tuple<int(*&)(int), int(*&)(int)>>::value << '\n';
}

c3, , , , , . , , , ret_type.

std::decay F.

+5

All Articles