簡體   English   中英

decltype為遞歸類型的遞歸可變參數函數模板

[英]decltype for the return type of recursive variadic function template

給出以下代碼(取自此處 ):

#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;
}

上面的代碼適用於C ++ 14。 我在使它適用於C ++ 11時遇到了一些麻煩。 我試圖為所涉及的功能模板正確提供返回類型,但沒有太大的成功,例如:

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)...);
}

對於上面的clang(3.5.0)給出了以下錯誤:

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.

如果我嘗試“選項2”。 我得到了幾乎相同的錯誤。

除了它看起來非常冗長之外,我似乎也無法做到正確。 誰能提供一些有關我做錯的見解? 有沒有更簡單的方法來提供返回類型?

第一個選項的錯誤消息是由於in的事實

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

你試圖用兩個double類型的參數(傳遞給operator()參數)來調用f1 func_type函數,但它需要一個std::pairfunc_type指的是元組中第一個func_type函數的類型)。

關於選項2,它不編譯的原因是尾部返回類型是函數聲明符的一部分,並且在看到聲明decltype(apply(...))結束之前不會將該函數視為聲明,因此您不能使用decltype(apply(...))在第一個apply聲明的尾隨返回類型中。


我相信你現在很高興知道為什么你的代碼不能編譯,但我想如果你有一個可行的解決方案,你會更高興。

我認為有一個重要的事實需要首先澄清: compose_impl applyoperator()模板的所有compose_impl 都有相同的返回類型 - 第一個 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;
}

筆記:

  • 它在C ++ 11模式和Visual C ++ 2013上編譯和使用GCC 4.9.1和Clang 3.5.0。
  • 如上所述, ret_hlp只處理聲明其operator()類似於lambda閉包類型的函數對象類型,但它可以很容易地擴展到其他任何東西,包括普通函數類型。
  • 我試圖盡可能少地改變原始代碼; 我覺得有一個需要提及的關於該代碼一個重要的一點:如果compose ,給予左值的參數(如本例) functionTuplecompose_impl會存儲這些參數引用 這意味着只要使用復合仿​​函數,原始仿函數就必須可用,否則你將有懸空參考。

編輯:以下是評論中要求的最后一個注釋的更多信息:

這種行為是由於方式轉發引用工作- Fs&& ...的功能參數compose 如果你有一個F&&形式的函數參數,正在進行模板參數推導(就像這里一樣),並且為該參數給出了一個類型為A參數,那么:

  • 如果參數表達式是一個rvalue ,則F被推導為A ,並且,當替換回函數參數時,它給出A&& (例如,如果你直接傳遞一個lambda表達式作為參數來compose ,就會發生這種情況);
  • 如果參數表達式是左值 ,則F推導為A& ,並且當替換回函數參數時,它給出A& && ,根據參考折疊規則產生A& (這是當前示例中發生的情況,如f1其他人都是左右)。

因此,在當前示例中, compose_impl將使用推導出的模板參數進行實例化(如使用發明的名稱用於lambda閉包類型)

compose_impl<lambda_1_type&, lambda_2_type&, lambda_3_type&>

這反過來會使functionTuple具有類型

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

如果你直接將lambda表達式作為參數傳遞給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;

結果是,函數對象將始終在元組內復制或移動,因此即使在原始組件被破壞后,也可以使用生成的組合函數對象。

哇,這很久了; 也許你不應該說'精心':-)。


編輯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指針。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM