簡體   English   中英

使用帶有可變參數模板函數的 decltype 的尾隨返回類型

[英]trailing return type using decltype with a variadic template function

我想編寫一個簡單的加法器(用於傻笑),將每個參數相加並返回具有適當類型的總和。 目前,我有這個:

#include <iostream>
using namespace std;

template <class T>
T sum(const T& in)
{
   return in;
}

template <class T, class... P>
auto sum(const T& t, const P&... p) -> decltype(t + sum(p...))
{
   return t + sum(p...);
}

int main()
{
   cout << sum(5, 10.0, 22.2) << endl;
}

在 GCC 4.5.1 上,這似乎適用於 2 個參數,例如 sum(2, 5.5) 返回 7.5。 但是,有比這更多的參數,我得到 sum() 尚未定義的錯誤。 但是,如果我像這樣聲明 sum() :

template <class T, class P...>
T sum(const T& t, const P&... p);

然后它適用於任意數量的參數,但 sum(2, 5.5) 將返回整數 7,這不是我所期望的。 有兩個以上的參數,我假設 decltype() 必須進行某種遞歸才能推斷出 t + sum(p...) 的類型。 這是合法的 C++0x 嗎? 還是 decltype() 僅適用於非可變參數聲明? 如果是這樣,您將如何編寫這樣的函數?

我認為問題在於可變參數函數模板僅您指定其返回類型后才被視為聲明因此decltype中的sum永遠不會引用可變參數函數模板本身。 但我不確定這是 GCC 錯誤還是 C++0x 根本不允許這樣做。 我的猜測是 C++0x 不允許在->decltype(expr)部分進行“遞歸”調用。

作為一種解決方法,我們可以使用自定義特征類避免->decltype(expr)這種“遞歸”調用:

#include <iostream>
#include <type_traits>
using namespace std;

template<class T> typename std::add_rvalue_reference<T>::type val();

template<class T> struct id{typedef T type;};

template<class T, class... P> struct sum_type;
template<class T> struct sum_type<T> : id<T> {};
template<class T, class U, class... P> struct sum_type<T,U,P...>
: sum_type< decltype( val<const T&>() + val<const U&>() ), P... > {};

這樣,我們可以用typename sum_type<T,P...>::type替換程序中的decltype並且它會編譯。

編輯:由於這實際上返回decltype((a+b)+c)而不是decltype(a+(b+c))這將更接近您如何使用加法,您可以用這個替換最后一個專業化:

template<class T, class U, class... P> struct sum_type<T,U,P...>
: id<decltype(
      val<T>()
    + val<typename sum_type<U,P...>::type>()
)>{};

C++14的解決方案:

template <class T, class... P>
decltype(auto) sum(const T& t, const P&... p){
    return t + sum(p...);
}

自動扣除返回類型。

在在線編譯器中查看

如果您想支持不同類型的引用,甚至更好:

template <class T, class... P>
decltype(auto) sum(T &&t, P &&...p)
{
   return std::forward<T>(t) + sum(std::forward<P>(p)...);
}

在在線編譯器中查看

如果您需要求和的自然順序(即(((a+b)+c)+d)而不是(a+(b+(c+d))) ),則解決方案更復雜:

template <class A>
decltype(auto) sum(A &&a)
{
    return std::forward<A>(a);
}

template <class A, class B>
decltype(auto) sum(A &&a, B &&b)
{
    return std::forward<A>(a) + std::forward<B>(b);
}

template <class A, class B, class... C>
decltype(auto) sum(A &&a, B &&b, C &&...c)
{
    return sum( sum(std::forward<A>(a), std::forward<B>(b)), std::forward<C>(c)... );
}

在在線編譯器中查看

顯然你不能以遞歸方式使用 decltype (至少目前,也許他們會修復它)

您可以使用模板結構來確定和的類型

它看起來丑陋但有效

#include <iostream>
using namespace std;


template<typename... T>
struct TypeOfSum;

template<typename T>
struct TypeOfSum<T> {
    typedef T       type;
};

template<typename T, typename... P>
struct TypeOfSum<T,P...> {
    typedef decltype(T() + typename TypeOfSum<P...>::type())        type;
};



template <class T>
T sum(const T& in)
{
   return in;
}

template <class T, class... P>
typename TypeOfSum<T,P...>::type sum(const T& t, const P&... p)
{
   return t + sum(p...);
}

int main()
{
   cout << sum(5, 10.0, 22.2) << endl;
}

使用 C++11 的std::common_type減少輸入的最后一個問題的另一個答案:只需使用

std::common_type<T, P ...>::type

作為您的可變參數總和的返回類型。

關於std::common_type ,這里是http://en.cppreference.com/w/cpp/types/common_type的摘錄:

對於算術類型,公共類型也可以被視為(可能是混合模式)算術表達式的類型,例如 T0() + T1() + ... + Tn()。

但顯然這僅適用於算術表達式,並不能解決一般問題。

我為接受的答案提供了這種改進。 只有兩個結構

#include <utility>

template <typename P, typename... Ps>
struct sum_type {
    using type = decltype(std::declval<P>() + std::declval<typename sum_type<Ps...>::type>());
};

template <typename P>
struct sum_type<P> {
    using type = P;
};

現在只需將您的函數聲明為

template <class T>
auto sum(const T& in) -> T
{
   return in;
}

template <class P, class ...Ps>
auto sum(const P& t, const Ps&... ps) -> typename sum_type<P, Ps...>::type
{
   return t + sum(ps...);
}

有了這個,你的測試代碼現在可以工作了

std::cout << sum(5, 10.0, 22.2, 33, 21.3, 55) << std::endl;

146.5

正確的做法:

#include <utility>

template <typename... Args>
struct sum_type;

template <typename... Args>
using sum_type_t = typename sum_type<Args...>::type;

template <typename A>
struct sum_type<A> {
    using type = decltype( std::declval<A>() );
};

template <typename A, typename B>
struct sum_type<A, B> {
    using type = decltype( std::declval<A>() + std::declval<B>() );
};

template <typename A, typename B, typename... Args>
struct sum_type<A, B, Args...> {
    using type = sum_type_t< sum_type_t<A, B>, Args... >;
};

template <typename A>
sum_type_t<A> sum(A &&a)
{
    return (std::forward<A>(a));
}

template <typename A, typename B>
sum_type_t<A, B> sum(A &&a, B &&b)
{
    return (std::forward<A>(a) + std::forward<B>(b));
}

template <typename A, typename B, typename... C>
sum_type_t<A, B, C...> sum(A &&a, B &&b, C &&...args)
{
    return sum( sum(std::forward<A>(a), std::forward<B>(b)), std::forward<C>(args)... );
}

https://coliru.stacked-crooked.com/a/a5a0e8019e40b8ba

這完全保留了結果類型的操作(甚至是 r 值引用)。 操作順序很自然: (((a+b)+c)+d)

對於 C++17:

template <class... P>
auto sum(const P... p){
    return (p + ...);
}

int main()
{
    std::cout << sum(1, 3.5, 5) << std::endl;
    return EXIT_SUCCESS;
}

閱讀折疊表達式

暫無
暫無

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

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