2015-04-24 68 views
4

給定一個具有單個模板參數T的模板類A,是否可以只重載A中可用於類型T的操作符?例如:如果模板參數只有重載操作符

template <typename T> 
class A 
{ 
public: 
    #if hasOperator(T, +=) 
    T& operator +=(const T &rhs) 
    { 
     mValue += rhs; 
     return mValue; 
    } 
    #endif 

private: 
    T mValue; 
} 


int main() 
{ 
    A<int> a; 
    a += 8; //+= will forward to the += for the int 

    struct Test { /*no operators defined*/ }; 
    A<Test> b; //+= is not implemented since Test does not implement += 
} 

我正在寫一個通用的包裝類,需要表現完全像模板類型。所以如果T有operator + =,A會(在編譯時)相應地超載+ =。是的,我可以繼續,只是在A中實現每個運算符,但是當T沒有特定的運算符時編譯器會出錯。起初我雖然模板專業化可能是答案,但這需要每種類型的專業化。雖然這可以工作,並且可以進行大量輸入,但它不會因爲A需要使用任何類型(不僅僅是專用的)。

+1

看起來你正在尋找['enable_if'](http://en.cppreference.com/w/cpp/types/enable_if) – NathanOliver

+0

@PiotrS。甚至沒有想到我急於回答:)但是,在不太可能的情況下,OP希望顯式實例化一個沒有實現'operator +'的類型的模板,上面的代碼將失敗。 – Praetorian

+0

@DanWatkins但是這只是一個通知,你可能希望這個操作符被排除在重載設置之外 –

回答

4

使用Expression SFINAE您operator+從重載決議集落,除非T定義operator+

template <typename T> 
class A 
{ 
private: 
    T mValue; 
public: 
    template<typename U=T> 
    auto operator +=(const U &rhs) 
     -> decltype(mValue += rhs) 
    { 
     mValue += rhs; 
     return mValue; 
    } 
}; 

Live demo

+0

我知道OP有錯,但'operator + ='應該總是返回對'this'的引用。 – o11c

+0

@ o11c是的,任何事情都是令人驚訝的,但我會離開它,因爲這是OP公佈的內容。 – Praetorian

+0

請注意,這可能是一個複雜的方式UB。也就是說,標準要求模板函數具有有效的實例化,或者程序生成不良,不需要診斷。它還要求未實例化模板類的未調用方法。兩者之間的謊言含糊不清。 – Yakk

1

你實際上並沒有做任何事情。模板類的單個成員函數在使用之前不會被instantiatiated。你說:

但是然後編譯器會出錯,當T沒有一定的操作符。

但是,當A<T>沒有錯誤時,不是更明確嗎?如果您有:

template <typename T> 
class A 
{ 
public: 
    A& operator +=(const T &rhs) 
    { 
     mValue += rhs; 
     return *this; 
    } 

    A& operator-=(const T &rhs) 
    { 
     mValue -= rhs; 
     return *this; 
    } 

    // etc 

private: 
    T mValue; 
}; 

,那麼這將只是工作:

int main() { 
    A<int> a; 
    a += 8; //+= will forward to the += for the int 

    struct Test { 
     Test& operator-=(const Test&) { return *this; } 
    }; 

    A<Test> b; 
    b -= Test{}; // totally fine 
    b += Test{}; // error: no match for += 
        // (operand types are 'main()::Test' and 'const main()::Test') 
} 
2

我會以降低複雜性和公用事業3個解決方案。最後的解決方案是最簡單的,也是最簡單的。


小,如果有用的話,元編程庫:

template<class Lhs, class Rhs> 
using plus_equal_result = decltype(std::declval<Lhs>()+=std::declval<Rhs>()); 

template<class Lhs, class Rhs> 
using can_plus_equal = can_apply< plus_equal_result, Lhs, Rhs >; 
template<class T> 
using can_self_plus_equal = can_plus_equal< T&, T const& >; 

這給我們返回true類型或假的一些不錯的特點:

template<class...>struct types{using type=types;}; 
namespace details { 
    template<template<class...>class Z, class types, class=void> 
    struct can_apply : std::false_type {}; 
    template<template<class...>class Z, class...Ts> 
    struct can_apply<Z,types<Ts...>,std::void_t<Z<Ts...>>> : 
    std::true_type 
    {}; 
} 
template<template<class...>class Z, class...Ts> 
using can_apply = details::can_apply<Z,types<Ts...>>; 

一種用於+=結果特質類型取決於+=是否有效。

template<class A, class T, bool b = can_self_plus_equal<T>{}> 
struct A_maybe_plus_equal {}; 
template<class A, class T> 
struct A_maybe_plus_equal<A, T, true> { 
    A& self() { return *static_cast<A*>(this); } 
    A& operator+=(T && t) 
    { 
    self().mResult += std::move(t); 
    return self(); 
    } 
    template<class U> 
    std::enable_if_t<can_plus_equal<T&,U>{},A&> operator+=(U && u) 
    { 
    self().mResult += std::forward<U>(u); 
    return self(); 
    } 
}; 

這給我們一個+=如果我們通過真實。

template <class T> 
class A: 
    public A_maybe_plus_equal<A<T>, T> 
{ 
    friend class A_maybe_plus_equal<A<T>, T>; 
public: 
    // nothing needed 
private: 
    T mValue; 
}; 

它給你一個+=重載需要const T&T&&或在右手邊,當且僅當T& += T const&一個U&&是有效的表達式。

這是「完美」的解決方案,但它很複雜。

請注意,每個操作員可以單獨完成,因此您沒有專業化的組合爆炸。


現在,有一個更容易的選項。它的缺點是它不支持右邊的基於{}的構建,並且在一些標準的閱讀下它是非法的。

它,但是,仍然SFINAE友好:

template <typename T> 
class A { 
public: 
    template<class U> 
    auto operator +=(U&&rhs) 
    -> decltype((std::declval<T&>()+=std::declval<U&&>()),void(),A&) 
    // or std::enable_if_t<can_plus_equal<T&,U>{},A&> 
    { 
    mValue += std::forward<U>(rhs); 
    return *this; 
    } 
private: 
    T mValue; 
}; 

這可以摺疊成以上選項,並給雙方{}和完善的轉發語法。我發現如果您有template理想轉發器,則可以刪除T const&

這是技術上未定義行爲的原因是標準要求所有模板函數至少有一組參數可以使其主體能夠編譯。在給定的類實例中,上面的模板+=可能沒有這樣的類型參數集,這使得程序不合格,不需要診斷(即UB)。

還有另一個規則,除非被調用,否則模板類的成員函數不會被實例化。有人認爲這個規則取代了我在最後一段中提到的規則。

另一個說法是,只要模板參數與封閉類以及模板方法本身混合在一起,這種方法就可能是合法的,導致它可實例化。我猜想這是標準委員會的意圖,但我不知道如何閱讀標準才能獲得此結果。

此參數也適用於答案#1中的plus_equal函數。該實現不需要那麼簡單。另外,#1提供{}基於+=的語法,這是使用它的一個實際原因。這個問題 - 這個程序在技術上是不健全的 - 是學術問題,因爲我使用的所有編譯器對這個構造都沒有問題。


這一段的第三段給了我們最後的選擇。沒做什麼。

template <typename T> 
class A { 
public: 
    A& operator +=(const T &rhs) { 
    mValue += rhs; 
    return *this; 
    } 
private: 
    T mValue; 
}; 

,這意味着你不能SFINAE測試+=不工作,但只要你不叫+=它「作品」。例如,這是vectoroperator<的工作方式。這是一個較低的「質量」解決方案,標準庫中的這種情況往往會隨着時間的推移而得到修復。

但是,作爲第一遍,最後的選擇通常是最好的。只有當你期望SFINAE的要求是上面的值得。


最終,C++ 1z引入了概念。我相信概念會使這個問題更加容易,因爲根據封閉類的類型參數來消除超載是std中的一個長期問題。