2015-06-14 34 views
2

下面的代碼編譯和運行:模板與專業化的std :: enable_if <>

#include <cinttypes> 
#include <cstdlib> 
#include <iostream> 
#include <limits> 
#include <sstream> 
#include <stdexcept> 

class UnsignedBox { 
public: 
    typedef std::uint64_t box_type; 

    template<typename UNSIGNED_TYPE, 
     typename std::enable_if< 
     std::numeric_limits<UNSIGNED_TYPE>::is_signed==false && 
     (sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type)), int>::type = 0 
    > 
    UNSIGNED_TYPE toUnsigned()const { 
     //We've established we're not returning a smaller type so we can just 
     //return our value. 
     return value; 
    } 

    template<typename UNSIGNED_TYPE, 
     typename std::enable_if<std::numeric_limits<UNSIGNED_TYPE>::is_signed==false && 
     (sizeof(UNSIGNED_TYPE) < sizeof(UnsignedBox::box_type)), int>::type = 0 
    > 
    UNSIGNED_TYPE toUnsigned()const { 
     //We are returning a smaller type so we need a range check. 
     if(value>static_cast<box_type>(std::numeric_limits<UNSIGNED_TYPE>::max())){ 
      std::ostringstream msg; 
      msg<<value<<'>'<< 
       static_cast<box_type>(std::numeric_limits<UNSIGNED_TYPE>::max()); 
      throw std::logic_error(msg.str()); 
     } 
     return value; 
    } 

    UnsignedBox(const box_type ivalue): value(ivalue){} 
private: 
    box_type value; 

}; 

int main(int argc, char*argv[]) { 

    UnsignedBox box(
     static_cast<UnsignedBox::box_type>( 
      std::numeric_limits<std::uint32_t>::max())+10 
     ); 

    std::uint64_t v(box.toUnsigned<std::uint64_t>()); 
    std::cout<<v<<std::endl; 

    try { 
     std::uint32_t v(box.toUnsigned<std::uint32_t>()); 
    }catch(const std::logic_error err){ 
     std::cout<<err.what()<<std::endl; 
    } 

    return EXIT_SUCCESS; 
} 

預期輸出(所有支持平臺):

4294967305 
4294967305>4294967295 

到目前爲止好。

但我真的想(代碼清晰)做的事:

就是聲明是這樣的:

template<typename UNSIGNED_TYPE> 
UNSIGNED_TYPE toUnsigned()const; 

然後提供專門的實現,例如:

template<typename UNSIGNED_TYPE, 
     typename std::enable_if< 
     std::numeric_limits<UNSIGNED_TYPE>::is_signed==false && 
     (sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type)), int>::type = 0 
    > 
    UNSIGNED_TYPE UnsignedBox::toUnsigned()const { 
     //We've established we're not returning a smaller type so we can just 
     //return our value. 
     return value; 
    } 


    template<typename UNSIGNED_TYPE, 
     typename std::enable_if<std::numeric_limits<UNSIGNED_TYPE>::is_signed==false && 
     (sizeof(UNSIGNED_TYPE) < sizeof(UnsignedBox::box_type)), int>::type = 0 
    > 
    UNSIGNED_TYPE UnsignedBox::toUnsigned()const { 
     //We are returning a smaller type so we need a range check. 
     if(value>static_cast<box_type>(std::numeric_limits<UNSIGNED_TYPE>::max())){ 
      std::ostringstream msg; 
      msg<<value<<'>'<< 
       static_cast<box_type>(std::numeric_limits<UNSIGNED_TYPE>::max()); 
      throw std::logic_error(msg.str()); 
     } 
     return value; 
    } 

,但我得到此錯誤:

xxx.cpp:nn:20: error: prototype for 'UNSIGNED_TYPE UnsignedBox::toUnsigned() const' does not match any in class 'UnsignedBox' 
     UNSIGNED_TYPE UnsignedBox::toUnsigned()const { 
        ^xxx.cpp:nn:23: error: candidate is: template<class UNSIGNED_TYPE> UNSIGNED_TYPE UnsignedBox::toUnsigned() const 
     UNSIGNED_TYPE toUnsigned()const; 
        ^

因爲如果你問我的

UNSIGNED_TYPE UnsignedBox::toUnsigned() const 

原型這是奇怪的是

UNSIGNED_TYPE toUnsigned()const; 

什麼我做錯了一場偉大的比賽?

PS:這不是實際的問題,但我的問題是一個類似的地方,我想根據在編譯時檢查的基本類型的屬性特定一些模板。

+1

注意:還有一個'std :: is_unsigned'特徵可能比使用'std :: numeric_limits'類更合適。 – davidhigh

回答

2

你不能用一個簽名聲明函數:

template<typename UNSIGNED_TYPE> 
UNSIGNED_TYPE toUnsigned() const; 

,然後用不同的簽名定義它:

template<typename UNSIGNED_TYPE, 
    typename std::enable_if< 
    std::numeric_limits<UNSIGNED_TYPE>::is_signed==false && 
    (sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type)), int>::type = 0 
> 
UNSIGNED_TYPE UnsignedBox::toUnsigned() const; 

第一個到那裏需要一個模板參數,第二個需要兩個 - 即使一個是默認的。兩者必須完全匹配。所以,你需要聲明:

template <typename UNSIGNED_TYPE, 
      typename = typename std::enable_if< 
         std::is_unsigned<UNSIGNED_TYPE>::value && 
         sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type) 
         >::type> 
UNSIGNED_TYPE toUnsigned() const; 

template <typename UNSIGNED_TYPE, 
      typename = typename std::enable_if< 
         std::is_unsigned<UNSIGNED_TYPE>::value && 
         sizeof(UNSIGNED_TYPE) < sizeof(UnsignedBox::box_type) 
         >::type> 
UNSIGNED_TYPE toUnsigned() const; 

然後是兩個定義。另外這本身並沒有工作,因爲我們正在重新定義有效的默認模板參數,所以你需要SFINAE返回類型,如:

template <typename UNSIGNED_TYPE> 
typename std::enable_if< 
    std::is_unsigned<UNSIGNED_TYPE>::value && 
    sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type), 
    UNSIGNED_TYPE>::type 
toUnsigned() const; 


template <typename UNSIGNED_TYPE> 
typename std::enable_if< 
    std::is_unsigned<UNSIGNED_TYPE>::value && 
    sizeof(UNSIGNED_TYPE) < sizeof(UnsignedBox::box_type), 
    UNSIGNED_TYPE>::type 
toUnsigned() const; 

雖然這可能是簡單的有一個toUnsigned()那基於sizeof轉發到其他兩個成員函數:

template <typename UNSIGNED_TYPE, 
      typename = typename std::enable_if<std::is_unsigned<UNSIGNED_TYPE>::value>::type> 
UNSIGNED_TYPE toUnsigned() const { 
    return toUnsigned<UNSIGNED_TYPE>(
     std::integral_constant<bool, 
      (sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type))>{}); 
} 

template <typename UNSIGNED_TYPE> 
UNSIGNED_TYPE toUnsigned(std::true_type /* bigger */); 

template <typename UNSIGNED_TYPE> 
UNSIGNED_TYPE toUnsigned(std::false_type /* smaller */); 
+0

@丹艾倫答案是你的聲明和定義必須匹配。現在他們沒有。 'enable_if'類型的佔位符必須位於兩個地方。所以要麼用'enable_if'聲明它,並用''或者其他方式定義它。此外,您遇到相同簽名的問題,所以更喜歡調度方法。 – Barry

+0

便士正在開始下降。當我包含2個聲明並提供2個實現時,它可以工作。我知道。我想要一個聲明來驅動類的聲明中有多少實現已經修補過它。但事實證明,這些聲明不僅僅需要在語義上匹配,而是在詞法上!如果我甚至將'std :: numeric_limits :: is_signed == false'更改爲'std :: numeric_limits :: is_signed ==(1 == 0)',它會抱怨原型。你知道我在哪裏可以看到模板聲明如何被視爲等效嗎? – Persixty

+1

@DanAllen C++標準中的「函數模板重載」([temp.over.link])部分。簡單的答案是,它不需要詞法匹配,模板參數名稱可以不同。但基本上就是這樣。 – Barry

0

繼巴里(上圖),我確定我的答案應該是什麼樣一些真知灼見:

#include <cinttypes> 
#include <cstdlib> 
#include <iostream> 
#include <limits> 
#include <sstream> 
#include <stdexcept> 


//Here is the real aim - a short and sweet class declaration pretty much free 
//of implementation junk and jiggery-pokery. 

class UnsignedBox { 
public: 
    typedef std::uint64_t box_type; 

    template<typename UNSIGNED_TYPE> UNSIGNED_TYPE toUnsigned()const; 

    UnsignedBox(const box_type ivalue): value(ivalue){} 
private: 
    box_type value; 
}; 

//Now things get a bit more verbose... 

namespace UnsignedBox_support { 

    template< 
     typename FROM_TYPE, 
     typename TO_TYPE, 
     bool IS_UNSIGNED=(std::numeric_limits<TO_TYPE>::is_signed==false), 
     bool FROM_IS_LARGER=(sizeof(FROM_TYPE)>sizeof(TO_TYPE)) 
    > 
    class ToUnsigned{ }; 

    template<typename FROM_TYPE,typename TO_TYPE> 
    class ToUnsigned<FROM_TYPE,TO_TYPE,true,false>{ 
     template<typename UNSIGNED_TYPE> 
      friend UNSIGNED_TYPE UnsignedBox::toUnsigned()const; 

     static TO_TYPE convert(const FROM_TYPE v){ 
      //No checking... 
      return static_cast<TO_TYPE>(v); 
     } 
    }; 

    template<typename FROM_TYPE,typename TO_TYPE> 
    class ToUnsigned<FROM_TYPE,TO_TYPE,true,true>{ 
     template<typename UNSIGNED_TYPE> 
      friend UNSIGNED_TYPE UnsignedBox::toUnsigned()const; 

     static TO_TYPE convert(const FROM_TYPE v){ 
      if(v>static_cast<FROM_TYPE>(std::numeric_limits<TO_TYPE>::max())){ 
       std::ostringstream msg; 
       msg<<v<<'>'<< 
        static_cast<FROM_TYPE>(std::numeric_limits<TO_TYPE>::max()); 
       throw std::logic_error(msg.str()); 
      }    
      return static_cast<TO_TYPE>(v); 
     } 
    }; 
} 

template<typename UNSIGNED_TYPE> UNSIGNED_TYPE UnsignedBox::toUnsigned()const{ 
    return UnsignedBox_support::ToUnsigned< 
     UnsignedBox::box_type,UNSIGNED_TYPE 
    >::convert(this->value);  
    //TEMPLATE USE DEBUGGING: 
    //If you find yourself here being told ToUnsigned has no member 
    //convert() then it's possible you're trying to implement 
    //this member for a signed data-type. 
} 

int main(int argc, char*argv[]) { 
    UnsignedBox box(
     static_cast<UnsignedBox::box_type>(std::numeric_limits<std::uint32_t>::max())+10 
    ); 
    std::uint64_t v(box.toUnsigned<std::uint64_t>()); 
    std::cout<<v<<std::endl; 

    try { 
     std::uint32_t v(box.toUnsigned<std::uint32_t>()); 
    }catch(const std::logic_error err){ 
     std::cout<<err.what()<<std::endl; 
    } 
    return EXIT_SUCCESS; 
} 

訣竅是實現一個成員函數(不能部分專用),並讓它呼叫到一個類(可以是部分專用的。

通知未聲明支持類中的成員convert()ToUnsigned濫用模板並試圖爲其調用某個簽名類型會引發編譯錯誤。否則,您很難追蹤鏈接錯誤。 我已經添加到您很可能將要採取的,如果你沒有這樣的行添加到main()點評論:

int xxx(box.toUnsigned<int>()); 

我必須說,我覺得這是更小的醜陋的黑客攻擊比任何std::enable_if<>解決方案,並通過讓支持成員私密並幫助實現它,至少可以「封裝」一些工作。

它也爲進一步的專業化打開了一扇門,可以補充或壓倒那些已經給予的東西。我並不是說所有的模板都應該寫成允許進一步的專業化,但我認爲這是敞開大門的有用工具。