2016-04-12 12 views
10

Below SFINAE Code mit variadische Vorlagen schön klappern mit kompiliert 3.7.1, C++ 14:SFINAE geschieht nicht mit 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); 
} 

Ich möchte die richtige Überlastung der foo rekursiv aufgerufen werden, basierend von der Art H:

  1. wenn std::begin(h) für einen h der Typ definiert ist H möchte ich die Überlastungs Nummer 1 gewählt
  2. werden
  3. wenn H „integraler Typ“ ist, möchte ich überlasten Nummer 2.

Dies funktioniert, wie es ist. Aber wenn ich eine weitere Überlastung für Enum Typen hinzufügen (Sie un-Kommentar dritten Überlastung versuchen kann), dann bekomme ich:

error: only enumeration types have underlying types

Ich bin damit einverstanden, dass nur Aufzählungen einen zugrunde liegenden Typ bekam, also warum ist Nicht die dritte Überlast (mit std::underlying_type) bekommen SFINAE-d weg?

+0

Warum gehst du nicht einfach verwenden 'std :: enable_if :: value> :: Baumuster zur? Es [kompiliert gut] (http://ideone.com/AeUzi9). Sind Sie spezifisch für 'enum' mit nur darunterliegenden Typen von 'uint8_t'? In diesem Fall können Sie eine weitere Bedingung von "sizeof (H) == sizeof (uint8_t)" hinzufügen. h. .. 'std :: is_enum :: Wert && (sizeof (H) == sizeof (uint8_t))'. Das ist im obigen Beispiel von ideone abgedeckt. – iammilind

+1

@iammilind: guter Rat, danke –

Antwort

13

std::underlying_type ist nicht SFINAE freundlich. Der Versuch, für einen Nicht-Aufzählungstyp auf std::underlying_type<T>::type zuzugreifen, führt zu undefiniertem Verhalten (häufig ein schwerer Fehler), nicht zu einem Substitutionsfehler.

Sie müssen sicherstellen, dass der fragliche Typ zuerst ein Aufzählungstyp ist, bevor Sie versuchen, auf den zugrunde liegenden Typ zuzugreifen. Dies in Zeile zu schreiben wäre etwas in Richtung typename std::enable_if<std::is_enum<H>::value, std::underlying_type<H>>::type::type. Ersetzen Sie die typename std::underlying_type<H>::type in Ihrem Rückgabetyp mit dieser abscheulichen Unordnung und Sie erhalten eine noch schrecklichere Unordnung, die funktioniert :)

Wenn Sie finden, dass Sie häufig tun müssen - oder einfach nicht schreiben möchten 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 - können Sie schreiben eine SFINAE freundliche 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 */> {};