2016-08-31 7 views
5

Tak więc projektuję rodzaj funkcji my_numeric_cast, aby ograniczyć typy konwersji dostępnych podczas korzystania z architektury, którą piszę.SFINAE do szablonu podstawowego zawsze powoduje błąd

To było dość proste do zrobienia czegoś jak

template<typename To, typename From> 
constexpr To my_numeric_cast(From); 

template<> 
constexpr float my_numeric_cast<float, int>(int i) { return i; } 

który pracuje, umożliwiając odlewanie tylko od wskazówki do pływaków, gdy używany jest obsada. I generowanie błędu powiązania za każdym razem, gdy podjęto próbę rzucenia spoza białej listy.

Jednak naprawdę chciałbym uczynić ten błąd kompilacją, aby złapać nadużycie znacznie szybciej.
W jaki sposób ustawić prawidłową treść szablonu podstawowego, czy można się tego spodziewać podczas tworzenia instancji?

+0

Dlaczego warto korzystać z szablonu? Możesz dodać przeciążenia i jeśli wywołają przeciążenie, które nie istnieje, jest to błąd kompilatora. – NathanOliver

+0

@NathanOliver, Nie można przeładować na typ zwracany przez funkcję. Powoduje to często niejednoznaczność. – StoryTeller

Odpowiedz

9

Nie możesz pisać specjalizację szablonu funkcyjnego dla których nie szablon argumentem sprawia, że ​​ciało ważnego w C++. Rezultatem jest źle sformułowany program, który nie wymaga diagnostyki. Obejmuje to specjalizację podstawową.

Większość odpowiedzi tutaj jest po prostu nieokreślonym zachowaniem. Mogą działać, ale nie są poprawne w C++. Mogą działać dzisiaj, ale po uaktualnieniu biblioteki uaktualnienia kompilatora lub innego celu kompilacji mogą zawieść w zupełnie inny i zaskakujący sposób. Poleganie na UB bez wyraźnego powodu jest złym pomysłem.

Na plus, możemy pozbyć się szablonu specjalizacji i rozwiązać problem za jednym zamachem:

template<class T>struct tag_t{}; // may need `constexpr tag_t(){}` on some compilers 
template<class T>constexpr tag_t<T> tag{}; 

template<class T, class F> 
constexpr T my_numeric_cast(F, tag_t<F>)=delete; // generates compile time error 

constexpr float my_numeric_cast(int i, tag_t<float>) { return i; } // not a template! Could be if you want it to be. 

template<typename To, typename From> 
constexpr To my_numeric_cast(From f){ 
    return my_numeric_cast(f, tag<To>); 
} 

i zrobione.

Generuje przyjazne wiadomości. Program jest dobrze sformułowany. Wdrażanie obsady nie jest już specjalizacją. Można go nawet zaimplementować w przestrzeni nazw typu, który jest rzutowany do lub od kiedy włączona jest funkcja ADL.

Jeśli rozwiążesz problem ze specjalizacją funkcji szablonu, zrób ponownie. Są delikatne, nie działają jak specjalizacja szablonów klas lub przeciążanie funkcji (chociaż wyglądają jak obie!) I zazwyczaj nie są najlepszym rozwiązaniem. Są wyjątki, kiedy może to być dobry pomysł, ale są one dość rzadkie i biorąc pod uwagę, jak rzadko unikają dziwacznej funkcji, mogą być warte tego.

+0

To było pouczające. Zrobiłem specjalizację, ponieważ był to podręcznikowy przykład, kiedy się nauczyłem. Dziękuję Ci! – StoryTeller

+0

+1 dla lubię użycie '= delete'. Tylko dlatego, że jestem ciekawa, czy moja odpowiedź jest nieodpowiednia, o czym wspomniałeś? Byłem pewien, że był solidny, może nieco zawikłany i znacznie gorszy od twojego, ale nie jest oparty na UB. Czy się mylę? – skypjack

+1

@skypjack nope. Twoja specjalizacja szablonu jest ważna dla co najmniej jednego typu. Reguła istnieje, o ile mogę powiedzieć, aby umożliwić (przyszłe iteracje) języka C++ (bez łamania zmian) lub sam kompilator, aby wykonać dodatkowe sprawdzenia w celu sprawdzenia kodu szablonu przed podstawieniem argumentu szablonu i błędem wcześnie. Wszystko, co sprawiłoby, że kod byłby nieważny z powodu * wszystkich * argumentów, jest w ten sposób uczciwą grą. – Yakk

3

Można użyć cechy dostać błąd czasu kompilacji:

template<typename, typename> struct RetTraits; 
// Enable it for int -> float 
template<> struct RetTraits<int, float> { using type = float; }; 

template<typename To, typename From> 
using RetType = typename RetTraits<To, From>::type; 

template<typename To, typename From> 
constexpr RetType<To, From> my_numeric_cast(From f) { 
    return To(f); 
} 

int main() { 
    my_numeric_cast<int, float>(42); 
    // This won't compile 
    // my_numeric_cast<int, int>(42); 
} 
0

To zaktualizowana odpowiedź, która zadziała w kodzie pre C++ 11. Nadal uważam, że odpowiedź jest znakomita, a także jego sugestie dotyczące specjalizacji szablonów funkcji, ale czułem, że powinienem to opublikować dla kompletności.

Jeśli to zrozumiesz, to rozwiązanie nie spowoduje nieprawidłowego utworzenia programu, a nawet da dość jasny komunikat o błędzie. Wykorzystuje koncepcję użycia szablonu klasy do oznaczenia szablonu podstawowego jako niekompletnego, ale nadal używa specjalizacji szablonów funkcji.

namespace detail { 
    template<typename T1, typename T2> struct InvalidInstantiation; 
} 

template<typename To, typename From> 
To my_cast (From f) { 
    detail::InvalidInstantiation<To, From> No_Function_Exists; 
} 

template<> 
float my_cast(int i) { return i; } 

int main() { 
    my_cast<float>(7); 
    //my_cast<int>(7.0); // This will result in an error 
    return 0; 
} 
Powiązane problemy