2016-04-12 9 views
10

poniższy kod SFINAE ze zmiennej liczbie argumentów szablonów kompiluje ładnie użyciu szczęk 3.7.1, C++ 14:SFINAE nie dzieje z std :: underlying_type

#include <array> 
#include <iostream> 
#include <vector> 
#include <cstdint> 

enum class Bar : uint8_t { 
    ay, bee, see 
}; 

struct S { 

static void foo() {} 

// std::begin(h) is defined for h of type H 
template<typename H, typename... T> 
static typename std::enable_if<std::is_pointer<decltype(std::begin(std::declval<H>()))*>::value>::type 
foo(const H&, T&&... t) 
{ std::cout << "container\n"; foo(std::forward<T>(t)...); } 

// H is integral 
template<typename H, typename... T> 
static typename std::enable_if<std::is_integral<typename std::remove_reference<H>::type>::value>::type 
foo(const H&, T&&... t) 
{ std::cout << "integer\n"; foo(std::forward<T>(t)...); } 

// H is an enum with underlying type = uint8_t 
/* 
template<typename H, typename... T> 
static typename std::enable_if<std::is_same<typename std::underlying_type<H>::type,uint8_t>::value>::type 
foo(const H&, T&&... t) 
{ std::cout << "enum\n"; foo(std::forward<T>(t)...); } 
*/ 
}; 


int main() 
{ 
    S::foo(std::array<int,8>(), 5, 5L, std::vector<int>{}, 5L); 
} 

chcę poprawnego przeciążenie foo być nazywany rekursywnie, w oparciu od rodzaju H:

  1. jeśli std::begin(h) jest zdefiniowany dla h typu H, chcę numer przeciążenie 1 do wyboru
  2. jeśli H jest „integralną typu” chcę przeciążać numer 2.

Działa to tak jak jest. Ale jeśli dodać kolejny przeciążenie dla wyliczenia typy (można spróbować un-comment trzecim przeciążenia), a następnie uzyskać:

error: only enumeration types have underlying types

zgadzam się, że tylko enums dostał typ podstawowy, a więc dlaczego jest Czy trzecie przeciążenie (z std::underlying_type) nie odbiera SFINAE-d?

+0

Dlaczego nie można po prostu użyć std :: enable_if ' :: wartość> :: type'? To [kompiluje się dobrze] (http://ideone.com/AeUzi9). Czy jesteś specyficzny dla 'wyliczeń 'z bazowymi typami' uint8_t' tylko? W takim przypadku możesz dodać 1 dodatkowy warunek "sizeof (H) == sizeof (uint8_t)". tj. 'std :: is_enum :: value && (sizeof (H) == sizeof (uint8_t))'. Jest to opisane w powyższym przykładzie ideowym. – iammilind

+1

@iammilind: dobra rada, dzięki –

Odpowiedz

13

std::underlying_type nie jest przyjazny dla SFINAE. Próba uzyskania dostępu do numeru std::underlying_type<T>::type dla typu bez wyliczenia powoduje niezdefiniowane zachowanie (często jest to błąd twardy), a nie awarię zastępowania.

Należy sprawdzić, czy dany typ jest typem wyliczeniowym przed próbą uzyskania dostępu do jego podstawowego typu. Zapisanie tego w linii byłoby czymś w rodzaju typename std::enable_if<std::is_enum<H>::value, std::underlying_type<H>>::type::type. Zastępując typename std::underlying_type<H>::type w swoim typie zwrotu z tym ohydnym bałaganem, a otrzymasz jeszcze bardziej odrażający bałagan, który działa :)

Jeśli często musisz to robić - lub po prostu nie chcesz pisać typename std::enable_if<std::is_same<typename std::enable_if<std::is_enum<H>::value, std::underlying_type<H>>::type::type, uint8_t>::value>::type - możesz pisać SFINAE przyjazne underlying_type:

template<class T, bool = std::is_enum<T>::value> 
struct safe_underlying_type : std::underlying_type<T> {}; 
template<class T> 
struct safe_underlying_type<T, false /* is_enum */> {};