2016-03-05 90 views
1

你好,對不起我的英文不好。g ++和clang ++與運算符<()重載的不同行爲

爲了使用C++ 11進行練習,我試圖編寫std :: experimental :: any(http://en.cppreference.com/w/cpp/experimental/any)類的一個版本,添加一些額外的東西。我在g ++(4.9.2)和clang ++(3.5.0)之間得到了不同的行爲。

以下是該類(以及使用的類)的縮寫版本,涵蓋了必需的最小值以及觸發問題的非常小的main()。

對不起,我沒有讓這個例子縮短。

#include <memory> 
#include <iostream> 
#include <type_traits> 
#include <unordered_set> 

namespace yans // yet another name space 
{ 
    class anyB // base for any 
    { 
     public: 

     virtual std::type_info const & typeT() const = 0; 
     virtual bool isLess (anyB const *) const = 0; 
    }; 

    template <typename T> 
     class anyD : public anyB // derived for any 
     { 
     private: 

      T val; 

      static std::type_info const & typeInfo() 
      { static auto const & ret = typeid(T); return ret; } 

      template <typename U> // preferred version 
       static auto lessF (U const & u1, U const & u2, int) 
       -> decltype( std::declval<U const &>() 
          < std::declval<U const &>()) 
       { return (u1 < u2); } 

      template <typename U> // emergency version 
       static auto lessF (U const &, U const &, ...) -> bool 
       { throw std::runtime_error("no operator < for type "); } 

     public: 

      anyD (T const & v0) 
       : val(v0) 
       { } 

      std::type_info const & typeT() const override final 
      { return typeInfo(); } 

      bool isLess (anyB const * pB0) const override final 
      { 
       auto pD0 = dynamic_cast<anyD<T> const *>(pB0); 

       if (nullptr == pD0) 
        throw std::bad_cast(); 

       return lessF(val, pD0->val, 0); 
      } 
     }; 

    class any 
    { 
     private: 
     template <class T> 
      using sT = typename std::decay<T>::type; 

     template <class T> 
      using noAny 
      = typename std::enable_if 
      <false == std::is_same<any, sT<T>>::value, bool>::type; 

     template <class T> 
      using isCpCtr 
      = typename std::enable_if 
      <true == std::is_copy_constructible<sT<T>>::value,bool>::type; 

     std::unique_ptr<anyB> ptr; 

     static std::type_info const & voidInfo() 
      { static auto const & ret = typeid(void); return ret; } 

     bool opLess (any const & a0) const 
      { 
      return 
        type().before(a0.type()) 
       || ( (type() == a0.type()) 
        && (false == empty()) 
        && ptr.get()->isLess(a0.ptr.get())); 
      } 

     public: 

     template <typename T, typename = noAny<T>, typename = isCpCtr<T>> 
      any (T && v0) 
      : ptr(new anyD<sT<T>>(std::forward<T>(v0))) 
      { } 

     bool empty() const noexcept 
      { return ! bool(ptr); } 

     std::type_info const & type() const 
      { return (ptr ? ptr->typeT() : voidInfo()); } 

     friend bool operator< (any const &, any const &); 
    }; 

    bool operator< (any const & a0, any const & a1) 
    { return a0.opLess(a1); } 
} 

int main() 
{ 
    try 
    { 
     yans::any ai { 12 }; 
     yans::any as { std::string("t1") }; 
     yans::any au { std::unordered_set<int> { 1, 5, 3 } }; 

     std::cout << "ai < 13 ? " << (ai < 13) << '\n'; 
     std::cout << "as < std::string {\"t0\"} ? " 
     << (as < std::string {"t0"}) << '\n'; 
     std::cout << "au < std::unordered_set<int> { 2, 3, 4 } ? " 
     << (au < std::unordered_set<int> { 2, 3, 4 }) << '\n'; 
    } 
    catch (std::exception const & e) 
    { 
     std::cerr << "\nmain(): standard exception of type \"" 
     << typeid(e).name() <<"\"\n" 
     << " ---> " << e.what() << " <---\n\n"; 
    } 

    return EXIT_SUCCESS; 
} 

的想法,後面操作者<(),是返回「true」表示左操作數的類型是小於右側的類型(根據typeid的(T)。之前())和,如果類型匹配,則返回通過比較包含值返回的值。我知道這是一個值得懷疑的解決方案,但我正在學習。

麻煩的是,在類的任何實例中都可以包含沒有運算符<()的類型的值。在這個例子中,類std :: unordered_set < int>的一個實例。然後我試圖開發一些重載(SFINAE)方法lessF();當運算符<()可用於包含的類型T時,首選返回比較值;操作員<()不可用時使用的緊急版本會引發異常。

隨着鏗鏘聲++,我得到了我所希望的:類anyD < std :: unordered_set < int >>沒有實現lessF的首選版本,並且比較從緊急版本中產生了一個異常。

與G ++,與此相反,類anyD <的std :: unordered_set < INT >>生成優惠版本調用上的任何兩個實例操作<(),建立在所述兩個標準:: unordered_set < INT > lessF()的參數(any的模板化構造函數不是「顯式」定義的),然後遞歸調用自身,進入循環並生成一個錯誤(「Errore di segmentazione」,即「段錯誤」) 。

我想了解的是:

  • 根據ISO C++ 11,這是正確的鏗鏘++的行爲或g的行爲++?我可以在本地阻止(僅在lessF())中,並且沒有聲明任何()的模板構造函數的「顯式」,兩個std :: unordered_set int>之間的比較將會在兩個嗎?換句話說:我怎樣才能防止在任何D < std :: unordered_set < int >>中開發lessF()的優先版本?

以下是兩個程序的輸出。

---- clang++ program output ---- 
ai < 13 ? 1 
as < std::string {"t0"} ? 0 
au < std::unordered_set<int> { 2, 3, 4 } ? 
main(): standard exception of type "St13runtime_error" 
    ---> no operator < for type <--- 

---- end output ---- 

---- g++ program output ---- 
ai < 13 ? 1 
as < std::string {"t0"} ? 0 
Errore di segmentazione 
---- end output ---- 

回答

3

我相信你已經發現了在海灣合作委員會的錯誤(我在更短的形式here複製它,敬請期待)。

問題是,如果仔細查看分段錯誤,您會看到unordered_set<int>operator<調用是無限遞歸的。這是因爲海灣合作委員會實際上認爲bool operator<(const any&, const any&)是匹配。它不應該在你叫它的地步。

最簡單的解決方法是簡單地確保operator<(const any&, const any&)只發現any的,不管你是在什麼命名空間簡單地定義移動到類:

class any { 
    friend bool operator< (any const & a0, any const & a1) { 
     return a0.opLess(a1); 
    } 
}; 

這是好事無論如何。

+0

@巴里,我認爲你是對的:這是一個可見性問題。 我把下列行虛擬類定義之前(anyB) '類中的任何;' '布爾操作符<(任何常量&A0,任何常量& a1);' ,現在還通過鐺產生++程序進入在一個循環,併產生分段錯誤 並祝賀你的例子:極其簡單 – max66

+0

@ Barry,但關於你的解決方案......呃...在你的方式我失去了能力比較任何類的實例與通用類型T;如示例程序 我認爲將顯式模板構造函數(並開發模板特化對於任何對象遇到通用T對象時的操作符)。但是,在std :: experiment :: any的規範中,模板構造函數不是顯式的。 – max66

+0

@ max66如果你按照我的建議去做,你仍然可以比較一個'any'和一個非'any'。 ADL會找到正確的'operator <'並執行轉換。 – Barry