2012-02-14 9 views
7

Mam funkcje do konwersji różnych typów arytmetycznych na typ zmiennoprzecinkowy o połowie precyzji (tylko uint16_t na najniższym poziomie) i mam różne funkcje dla typów źródeł całkowitych i zmiennoprzecinkowych, używając SFINAE i std::enable_if :SFINAE rozróżnienie między znakiem i unsigned

template<typename T> 
uint16_t to_half(typename std::enable_if< 
       std::is_floating_point<T>::value,T>::type value) 
{ 
    //float to half conversion 
} 

template<typename T> 
uint16_t to_half(typename std::enable_if< 
       std::is_integral<T>::value,T>::type value) 
{ 
    //int to half conversion 
} 

nazywane są wewnętrznie z uniwersalnej matrycy przez konstruktora wyraźnej instancji:

template<typename T> 
half::half(T rhs) 
    : data_(detail::conversion::to_half<T>(rhs)) 
{ 
} 

To kompiluje i również działa dobrze. Teraz staram się odróżnić podpisanych i niepodpisanych liczb całkowitych, zastępując drugą funkcję z dwóch funkcji:

template<typename T> 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
       std::is_signed<T>::value,T>::type value) 
{ 
    //signed to half conversion 
} 

template<typename T> 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
       std::is_unsigned<T>::value,T>::type value) 
{ 
    //unsigned to half conversion 
} 

Ale gdy próbuję skompilować ten VS2010 daje mi

błędu C2995: "uint16_t math::detail::conversion::to_half(std::enable_if<std::tr1::is_integral<_Ty>::value && std::tr1::is_signed<_Ty>::value, T>::type)": funkcję szablon już zdefiniowany.

Wygląda na to, że nie można rozróżnić dwóch szablonów, ale oczywiście nie miał problemu z wersją integralną obok wersji zmiennoprzecinkowej.

Ale ponieważ nie jestem, że dużo szablon mag może być po prostu brakuje czegoś oczywiste tutaj (albo powinna ona faktycznie działa i jest po prostu bug VS2010). Dlaczego więc ta praca nie działa i jak można ją wykonać przy jak najmniejszej ilości nakładów na programowanie oraz w granicach funkcji standardowych (jeśli to możliwe)?

+1

Nie jest jasne, czy 'is_signed' /' is_unsigned' wzajemnie się wyklucza (witaj 'char'?). Postaraj się, aby druga wersja wypowiedziała '! Std :: is_signed :: value'. –

+0

Czy możesz spróbować użyć 'std :: is_signed :: value' dla jednego z członków i'! Std :: is_signed :: value' dla drugiego? Ma to na celu upewnienie się, że nie ma jakiegoś typu, który ma niespójne ustawienia dla 'is_signed' i' is_unsigned'. –

+0

@KerrekSB & Dietmar Hah, to zrobili! Nie mogę uwierzyć, że to było takie łatwe. Jeśli ktoś doda to jako odpowiedź, zaakceptuję to. –

Odpowiedz

3

Jeśli to nie działa, oznacza to błąd kompilatora.

Dwa wyrażenia obejmujące parametry szablonu są uważane za równoważne, jeżeli dwie definicje funkcji zawierające wyrażenia będzie spełniać wymagania reguły jedna definicja ...

To najważniejszą regułę do rozważenia tutaj (pominięte szczegóły "..."). Twoje dwa szablony nie spełniają ODR, ponieważ ich sekwencje tokenów są różne.

dwa szablony funkcyjne są równoważne, jeśli są one zadeklarowane w tym samym zakresie, mają taką samą nazwę, mają identyczne listy parametrów szablonu, i typów zwracanych i listy parametrów, które są równoważne z wykorzystaniem reguł opisanych powyżej porównać wyrażeń obejmujących parametry szablonu.

Twoje dwa szablony definiują różne szablony i nie kolidują ze sobą. Teraz możesz sprawdzić, czy Twoje szablony są "funkcjonalnie równoważne". Byłoby, gdyby dla dowolnego możliwego zestawu argumentów szablonu, twoje wyrażenie enable_if zawsze dawało taką samą wartość. Ale to nie jest prawda dla is_unsigned i is_signed, tak też nie jest. Gdyby tak było, twój kod byłby źle sformułowany, ale nie wymagałby diagnozy (co w praktyce oznacza "niezdefiniowane zachowanie").

+0

Zastąpienie 'is_unsigned' z'! Is_signed' (lub na odwrót) działało, więc domyślam się, że (choć nie jest on tak dobrze zorientowany w głąb specyfikacji językowej, żeby nie mówić o szablonach), jest jakiś rodzaj, który jest podpisany i usigned. A może to możliwe, ponieważ domyślne wersje tych szablonów są oceniane jako "fałsz" dla typów niearametrycznych? Ale znowu jest też "is_integral", aby rozróżniać rzeczy (w przypadku typów integralnych powinno się wzajemnie wykluczać, czyż nie?). –

+0

@ Chrześcijanie Nie mam pojęcia, co robią, ale to zdecydowanie niewłaściwe zachowanie. Spróbuj '!! is_unsigned' zamiast'! Is_signed'. Nie zdziwiłbym się, gdyby to "działało" również :) –

+0

Hah, to też zadziałało. Ok teraz myślę, że to naprawdę robi się trochę śmieszne. Prawdopodobnie masz rację, ponieważ kompilator jest tu winny. –

7

Osobiście wolałbym uniknąć SFINAE tutaj tak dużo, jak to możliwe, ponieważ można osiągnąć to samo z przeciążeniem:

template<typename T> 
uint16_t to_half_impl(T val, std::true_type, std::true_type) 
{ 
    // is_integral + is_signed implementation 
} 

template<typename T> 
uint16_t to_half_impl(T val, std::true_type, std::false_type) 
{ 
    // is_integral + is_unsigned implementation 
} 

template<typename T> 
uint16_t to_half_impl(T val, std::false_type, std::true_type) 
{ 
    // is_floating_point implementation 
} 

template<typename T> 
typename std::enable_if<std::is_arithmetic<T>::value, uint16_t>::type to_half(T val) 
{ 
    return to_half_impl(val, std::is_integral<T>(), std::is_signed<T>()); 
} 
+0

+1 Dobra alternatywa. Ale na marginesie uważam, że ostatnim argumentem wersji float powinno być 'std :: true_type', ponieważ float są zawsze podpisywane (przynajmniej zwykłe implementacje, dla nie-IEEE mój kod konwersji i tak nie zadziała). –

+0

@Christian: Masz całkowitą rację; Opierałem się na logice dokumentów MSDN dla 'is_signed', które okazały się błędne (niespodzianka, niespodzianka). Naprawiony. – ildjarn

1

Im bardziej powszechne idiom jest użycie SFINAE na typ zwracany zamiast argumentu rodzaj. W przeciwnym razie, szablon typu T nie może być dedukowalny. Z

// from C++14 
template<bool C, typename T> using enable_if_t = typename std::enable_if<C,T>::type; 

template<typename T> 
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, uint16_t> 
to_half(T value) 
{ 
    //signed to half conversion 
} 

template<typename T> 
enable_if_t<std::is_integral<T>::value && !std::is_signed<T>::value, int16_t> 
to_half(T value) 
{ 
    //unsigned to half conversion 
} 

typu T w następującym stwierdzeniem

auto y=to_half(x); // T is deduced from argument, no need for <T> 

jest wywnioskować (nawet trywialnie), ale do oryginalnego kodu nie jest! Rzeczywiście, gdy uruchomiony to oświadczenie z implementacji to_half() przez brzękiem daje

test.cc:24:11: error: no matching function for call to 'to_half' 
    auto x= to_half(4); 
      ^~~~~~~ 
test.cc:7:10: note: candidate template ignored: couldn't infer template argument 'T' 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
     ^
test.cc:15:10: note: candidate template ignored: couldn't infer template argument 'T' 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
     ^

Oczywiście, jeśli ktoś wyraźnie stanowi argument szablonu (jak ty), problem ten nie występuje. Twój kod nie był zły (ale kompilator), ale jaki jest sens SFINAE, jeśli przekazujesz typ argumentu szablonu?

+0

Czy nie powinno to mieć takich samych problemów z niejednoznacznością jak SFINAE w typie argumentu? Czy istnieje pewna zasada, która sprawia, że ​​to działa, ale wersja argumentowa nie jest? W przeciwnym razie nie wydaje się tak bardzo podchodzić do tego pytania. –

+0

Istnieje ** bardzo dobry powód ** nie używać SFINAE dla typu argumentu, patrz edytowana odpowiedź. – Walter

Powiązane problemy