使用標準庫中的Type Trait來檢查傳入型別錯誤

Universal Reference(UR)由於是用template撰寫而成的,因此他會在編譯期根據傳入的形態動態產生所需的程式碼。這可以說是一把雙面刃,因為就介面的宣告來看,它可以傳入任何的形態,若是使用者傳入不是我們預期中形態的話,雖然編譯器會報錯,但同時也會傳出一大串難解的錯誤訊息。若是系統複雜一點,一下子噴出上千行的錯誤訊息也不是什麼奇怪的事。雖然認真追還是能追到錯誤的源頭,但程序猿們平常的生活已經夠苦悶了,為什麼不讓生活簡單一點呢?這時候我們就必須要藉助Concept Check的概念,來幫助我們減少錯誤訊息。什麼是Concept Check?可以參考我以前寫過的文章:

[insert page=‘233’ display=‘link’]

 

首先,在導入Concept Check前的程式碼如下:

 

class URWidget{ public: URWidget(){};

    template<typename T>
    void setResource(T&& newResoure) // universal reference
    {
    m_resourcePtr = std::forward<T>(newResoure);
}
    template<typename T>
    void setName(T&& newName) // universal reference
    {
        m_name = std::forward<T>(newName);
    }
static URWidget makeWidget() // Moving version of makeWidget
{
    URWidget w;
    //do somthing
        return std::move(w); // move w into return value
    } // (don't do this!)

private:

    std::string m_name;
    std::shared_ptr<Resource> m_resourcePtr;

};

int main(){

URWidget widget;
auto newRes = std::make_shared<Resource>("test");
widget.setResource(newRes); //ok

widget.setResource("test"); //oops! should compile error
    return 0;
}

 

 

我們可以看到,如果使用者丟入下面這一行的話:

widget.setResource(“test”); //oops! should compile error

 

編譯器馬上噴出一堆意義不明的錯誤:

c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(70): error C2679: binary ’=’ : no operator found which takes a right-hand operand of type ‘const char [5]’ (or there is no acceptable conversion) 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(613): could be ‘std::shared_ptr &std::shared_ptr::operator =(const std::shared_ptr &) throw()’ 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(595): or ‘std::shared_ptr &std::shared_ptr::operator =(std::shared_ptr &&) throw()’ 1> while trying to match the argument list ‘(std::shared_ptr, const char [5])’ 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(104) : see reference to function template instantiation ‘void URWidget::setResource<const char(&)[5]>(T)’ being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] ======== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ========

 

這時候苦命的程序猿就要想辦法去找出到底哪一行出了錯,藉由現代強大IDE的幫助,我們輕鬆的點擊錯誤訊息,並立刻跳到錯誤的那一行:

 

可是,在追蹤錯誤的過程,我們可以看到有二個錯誤根本是用來擾亂我們心神的存在:

c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(613): could be ‘std::shared_ptr &std::shared_ptr::operator =(const std::shared_ptr &) throw()’ 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(595): or ‘std::shared_ptr &std::shared_ptr::operator =(std::shared_ptr &&) throw()’ 1> while trying to match the argument list ‘(std::shared_ptr, const char [5])’

 

第一次看到的時候,相信沒有人可以馬上反應過來:這memory(613)行到底是錯啥。我們必須一步一步的追到錯誤的最底層才會發現,原來這個function不能丟string進去。這裡的例子還算是簡單,就讓我們試試稍微增加一點系統複雜度的話,看看會怎麼樣:

class URWidget{ public: URWidget(){};

    template<typename T>
    void setResource(T&& newResoure) // universal reference
    {
    setResource2(std::forward<T>(newResoure));
   // m_resourcePtr = std::forward<T>(newResoure);
}
    template<typename T>
    void setName(T&& newName) // universal reference
    {
        m_name = std::forward<T>(newName);
    }
static URWidget makeWidget() // Moving version of makeWidget
{
    URWidget w;
    //do somthing
        return std::move(w); // move w into return value
    } // (don't do this!)

private:

    template<typename T>
    void setResource2(T&& newResoure){ // universal reference
        setResource3(std::forward<T>(newResoure));
    }
    template<typename T>
    void setResource3(T&& newResoure){ // universal reference
    setResource4(std::forward<T>(newResoure));
}
    template<typename T>
    void setResource4(T&& newResoure){ // universal reference
        m_resourcePtr = std::forward<T>(newResoure);
    }

private:

    std::string m_name;
    std::shared_ptr<Resource> m_resourcePtr;

};

 

傳遞參數的路徑如下:setResourcesetResource2setResource3setResource4。編譯後錯誤訊息馬上增加了:

1>------ Build started: Project: Tuto4_UR_and_Type_Trait, Configuration: Debug Win32 ------ 1> Source.cpp 1>c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(105): error C2679: binary ’=’ : no operator found which takes a right-hand operand of type ‘const char [5]’ (or there is no acceptable conversion) 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(613): could be ‘std::shared_ptr &std::shared_ptr::operator =(const std::shared_ptr &) throw()’ 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(595): or ‘std::shared_ptr &std::shared_ptr::operator =(std::shared_ptr &&) throw()’ 1> while trying to match the argument list ‘(std::shared_ptr, const char [5])’ 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(101) : see reference to function template instantiation ‘void URWidget::setResource4<const char(&)[5]>(T)’ being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(94) : see reference to function template instantiation ‘void URWidget::setResource3<const char(&)[5]>(T)’ being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(72) : see reference to function template instantiation ‘void URWidget::setResource2<const char(&)[5]>(T)’ being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(121) : see reference to function template instantiation ‘void URWidget::setResource<const char(&)[5]>(T)’ being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] ======== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ========

 

嗯…頭越來越痛了。當系統複雜度增加的時候,可以預見錯誤訊息的行數將會以指數複雜度噴出來。可不可以只顯示我到底錯在哪裡就行了?第一個想法,可能會想寫一個concept check的function來幫我們達成這件事。其實大可放心,因為標準庫中的Type Trait已經實作了幾乎所有我們需要的功能。在這裡的例子,我們只需要檢查的是傳進來的參數是不能夠assign給m_resourcePtr,因此我們只要增加一行實作:

template<typename T>
void setResource(T&& newResoure) // universal reference
{
static_assert(std::is_convertible<T, decltype(m_resourcePtr) >::value,
    "T can't assign to m_resourcePtr");
setResource2(std::forward<T>(newResoure));

}

 

 

錯誤訊息馬上只剩下二行:

1>------ Build started: Project: Tuto4_UR_and_Type_Trait, Configuration: Debug Win32 ------ 1> Source.cpp 1>c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(69): error C2338: T can’t assign to m_resourcePtr 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(116) : see reference to function template instantiation ‘void URWidget::setResource<const char(&)[5]>(T)’ being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] ======== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ========

 

錯誤發生在source.cpp(116),原因是T can’t assign to m_resourcePtr。

太棒了!看來頭痛藥可以少吃一點了!

 

本篇程式碼可以在github下載: https://github.com/dorgonman/UniversalReference VS專案為Tuto4_UR_and_Type_Trait