2015-03-17 8 views
1

Я работаю над созданием информации времени компиляции о классах, которые переносят другие классы на C++. В минимальном примере задачи я хотел спросить о, такой класс-оболочка:Сбой SFINAE с typedef в шаблоне класса, ссылающийся на typedef в другом шаблоне класса

  • содержит typedef WrappedType определения типа обернутого класса; и
  • перегружает шаблон структуры с именем IsWrapper, чтобы указать, что это класс-оболочка.

Существует шаблон структуры, называемый WrapperTraits, который затем может использоваться для определения корневого типа (без оболочки) иерархии обернутых типов. Например. если класс-оболочка является шаблоном класса Wrapper<T>, то корневой тип Wrapper<Wrapper<int>> будет int.

В нижеприведенном фрагменте кода я реализовал рекурсивный шаблон структуры с именем GetRootType<T>, который определяет typedef RootType, который дает корневой тип типа обертки T. Данное определение WrapperTraits содержит только корневой тип, заданный GetRootType, но на практике у него будут дополнительные члены. Чтобы проверить это, я написал обычную функцию f, которая принимает int и перегруженный шаблон функции f, который принимает произвольный класс-оболочку, который имеет int в качестве его корневого типа. Я использовал SFINAE для различения между ними, используя std::enable_if в типе возвращаемого шаблона шаблона, чтобы проверить, не является ли корневой тип аргумента f аргументом int (если аргумент f не является оберткой, попытка определить его корневой тип не удастся). Перед тем, как задать свой вопрос, вот фрагмент кода:

#include <iostream> 
#include <type_traits> 


// Wrapper ####################################### 

template<class T> 
struct Wrapper {typedef T WrappedType;}; 

template<class T, class Enable=void> 
struct IsWrapper: std::false_type {}; 

template<class T> 
struct IsWrapper<Wrapper<T> >: std::true_type {}; 


// WrapperTraits ####################################### 

template< 
    class T, 
    bool HasWrapper= 
    IsWrapper<T>::value && IsWrapper<typename T::WrappedType>::value> 
struct GetRootType { 
    static_assert(IsWrapper<T>::value,"T is not a wrapper type"); 

    typedef typename T::WrappedType RootType; 
}; 

template<class T> 
struct GetRootType<T,true> { 
    typedef typename GetRootType<typename T::WrappedType>::RootType RootType; 
}; 

template<class T> 
struct WrapperTraits { 
    typedef typename GetRootType<T>::RootType RootType; 
}; 


// Test function ####################################### 

void f(int) { 
    std::cout<<"int"<<std::endl; 
} 

// #define ROOT_TYPE_ACCESSOR WrapperTraits // <-- Causes compilation error. 
#define ROOT_TYPE_ACCESSOR GetRootType // <-- Compiles without error. 

template<class T> 
auto f(T) -> 
    typename std::enable_if< 
    std::is_same<int,typename ROOT_TYPE_ACCESSOR<T>::RootType>::value 
    >::type 
{ 
    typedef typename ROOT_TYPE_ACCESSOR<T>::RootType RootType; 

    std::cout<<"Wrapper<...<int>...>"<<std::endl; 
    f(RootType()); 
} 


int main() { 
    f(Wrapper<int>()); 
    return 0; 
} 

компилируется правильно (try it here) и производит вывод:

Wrapper<...<int>...> 
int 

Однако, я использовал GetRootType, чтобы определить тип корня в позвоните по номеру std::enable_if. Если я вместо этого использовать WrapperTraits, чтобы определить тип корневой (вы можете сделать это, изменив определение ROOT_TYPE_ACCESSOR), GCC выдает следующее сообщение об ошибке:

test.cpp: In instantiation of ‘struct WrapperTraits<int>’: 
test.cpp:49:6: required by substitution of ‘template<class T> typename std::enable_if<std::is_same<int, typename WrapperTraits<T>::RootType>::value>::type f(T) [with T = int]’ 
test.cpp:57:15: required from ‘typename std::enable_if<std::is_same<int, typename WrapperTraits<T>::RootType>::value>::type f(T) [with T = Wrapper<int>; typename std::enable_if<std::is_same<int, typename WrapperTraits<T>::RootType>::value>::type = void]’ 
test.cpp:62:19: required from here 
test.cpp:21:39: error: ‘int’ is not a class, struct, or union type 
    bool HasWrapper=IsWrapper<T>::value && IsWrapper<typename T::WrappedType>::value> 

Мой вопрос: , что правила о том, вывод аргумента в стандарте C++ объясните, почему использование WrapperTraits вызывает ошибку компиляции, но с использованием GetRootType нет? Обратите внимание, что я хочу, чтобы просить об этом, это уметь понять , почему возникает ошибка компиляции. Я не так заинтересован в том, что изменения могут быть сделаны, чтобы заставить его работать, как я уже знаю, что изменить определение WrapperTraits к этому исправляет ошибку:

template< 
    class T, 
    class Enable=typename std::enable_if<IsWrapper<T>::value>::type> 
struct WrapperTraits { 
    typedef typename GetRootType<T>::RootType RootType; 
}; 

template<class T> 
struct WrapperTraits<T,typename std::enable_if<!IsWrapper<T>::value>::type> { 
}; 

Однако, если кто-нибудь может увидеть более элегантный способ написания f и WrapperTraits, мне было бы очень интересно это посмотреть!

ответ

3

Проблема у Вас есть это из-за того, что SFINAE происходит только в «непосредственном контексте» (термин, который стандарт использует, но не определяет хорошо) экземпляра шаблона. Консоциация WrapperTraits<int>- это в непосредственном контексте экземпляра auto f<int>() -> ..., и он преуспевает. К сожалению, WrapperTraits<int> имеет плохо образованный участник RootType. Мгновением этого члена является не в непосредственном контексте, поэтому SFINAE не применяется.

Чтобы получить эту SFINAE работать, как вы собираетесь, вы должны организовать WrapperTraits<int> к не иметь типа с именем RootType, вместо того, чтобы такой членом, но с некорректными определениями. Вот почему ваша исправленная версия работает, как задумана, хотя вы могли бы сэкономить повторение переназначения:

template<class T, class Enable=void> 
struct WrapperTraits {}; 

template<class T> 
struct WrapperTraits<T,typename std::enable_if<IsWrapper<T>::value>::type> { 
    typedef typename GetRootType<T>::RootType RootType; 
}; 

Я бы, наверное, написать код системы целых черт, как (DEMO):

// Plain-vanilla implementation of void_t 
template<class...> struct voider { using type = void; }; 
template<class...Ts> 
using void_t = typename voider<Ts...>::type; 

// WrapperTraits ####################################### 

// Wrapper types specialize WrappedType to expose the type they wrap; 
// a type T is a wrapper type iff the type WrappedType<T>::type exists. 
template<class> struct WrappedType {}; 

// GetRootType unwraps any and all layers of wrappers. 
template<class T, class = void> 
struct GetRootType { 
    using type = T; // The root type of a non-WrappedType is that type itself. 
}; 

// The root type of a WrappedType is the root type of the type that it wraps. 
template<class T> 
struct GetRootType<T, void_t<typename WrappedType<T>::type>> : 
    GetRootType<typename WrappedType<T>::type> {}; 

// non-WrappedTypes have no wrapper traits. 
template<class T, class = void> 
struct WrapperTraits {}; 

// WrappedTypes have two associated types: 
// * WrappedType, the type that is wrapped 
// * RootType, the fully-unwrapped type inside a stack of wrappers. 
template<class T> 
struct WrapperTraits<T, void_t<typename WrappedType<T>::type>> { 
    using WrappedType = typename ::WrappedType<T>::type; 
    using RootType = typename GetRootType<T>::type; 
}; 

// Convenience aliases for accessing WrapperTraits 
template<class T> 
using WrapperWrappedType = typename WrapperTraits<T>::WrappedType; 
template<class T> 
using WrapperRootType = typename WrapperTraits<T>::RootType; 


// Some wrappers ####################################### 

// Wrapper<T> is a WrappedType 
template<class> struct Wrapper {}; 
template<class T> 
struct WrappedType<Wrapper<T>> { 
    using type = T; 
}; 

// A single-element array is a WrappedType 
template<class T> 
struct WrappedType<T[1]> { 
    using type = T; 
}; 

// A single-element tuple is a WrappedType 
template<class T> 
struct WrappedType<std::tuple<T>> { 
    using type = T; 
}; 

Хотя есть там есть много механизмов, и это может быть более тяжелое, чем вам нужно. Например, шаблон WrapperTraits, вероятно, можно было бы исключить, просто используя WrappedType и GetRootType. Я не могу себе представить, что вам часто нужно передать экземпляр WrapperTraits.

+0

Отлично, спасибо за ваш ответ и код! Да, я видел немного о «непосредственном контексте» в разделе 14.8.2 параграфа 8 проекта стандарта и задался вопросом, может ли это быть связано с этой ошибкой, но мне было не совсем понятно, что они подразумевают под «непосредственный контекст», поэтому не мог точно сказать, было ли это причиной. – Ose

+0

Он говорит, что «побочные эффекты, такие как создание специализированных шаблонов классов ... не находятся в« непосредственном контексте », хотя, по-видимому, это означает, что создание« GetRootType »не находится в непосредственном контексте попытка подстановки '' T = int'' при оценке '' '' '' '' '' '' '' '' '' '' '' '' '' '' подпись '', как ваш ответ, кажется, предлагает, так что я буду принимать ваш ответ. Было бы полезно, если бы стандарт дал более четкое определение «непосредственного контекста», хотя! – Ose

5

Первая часть вашего вопроса - почему она терпит неудачу. Ответ заключается в том, что на уровне времени компиляции && не обладает свойствами короткого замыкания.Эта линия:

bool HasWrapper=IsWrapper<T>::value && IsWrapper<typename T::WrappedType>::value> 

терпит неудачу, потому что первое условие false, но компилятор пытается инстанцирует вторую часть, но T является int и, следовательно, T::WrappedType не работает.


Чтобы ответить на вторую часть вашего вопроса о том, как сделать это проще, я думаю, что следующий должен сделать вас вполне устраивает:

#include <iostream> 
#include <type_traits> 

// Wrapper ####################################### 

template<class T> 
struct Wrapper {}; 

// All you need is a way to unwrap the T, right? 

template<class T> 
struct Unwrap { using type = T; }; 

template<class T> 
struct Unwrap<Wrapper<T> > : Unwrap<T> {}; 

// Test function ####################################### 

void f(int) { 
    std::cout<<"int"<<std::endl; 
} 

// Split unwrapping and checking it with enable_if<>: 
template<class T,class U=typename Unwrap<T>::type> 
auto f(T) -> 
    typename std::enable_if< 
    std::is_same<int,U>::value 
    >::type 
{ 
    std::cout<<"Wrapper<...<int>...>"<<std::endl; 
    f(U()); 
}  

int main() { 
    f(Wrapper<int>()); 
    return 0; 
} 

Live example

+0

Спасибо за ваш ответ! Теперь я понимаю, что мой вопрос неясен в отношении нескольких вещей - я скоро его обновлю. Что касается первой части, то суть заключается в том, что я хочу, чтобы компилятор попытался заменить '' int'' на '' T'', поймите, что '' T :: WrappedType'' является недопустимым выражением и затем отклоняет подстановку под SFINAE. Это похоже на то, что происходит, когда '' ROOT_TYPE_ACCESSOR'' '' GetRootType'', и компиляция фрагмента. Но для этого не происходит, когда '' ROOT_TYPE_ACCESSOR'' '' WrapperTraits''. Мне непонятно, что делает этот последний случай таким разным. – Ose

+0

Ваш ответ на вторую часть действительно радует меня! На практике у меня есть ряд несвязанных классов, которые ведут себя как '' Wrapper'', поэтому мне нужны выражения типа '' WrapperTraits >>> :: RootType'' для оценки '' int'', но ваше решение может быть распространено на этот случай, просто специализируясь на '' Unwrap'' для каждого класса '' WrapperX''. Я добавил +1 к вашему ответу, но не принял его, потому что он не отвечает на главный вопрос, о котором я спрашивал (т. Е. В первой части). – Ose

+0

@Ose Рад, что вам понравилась вторая часть, и пока я собирался прояснить первую часть, Кейси уже ответил, что (+1 для него) :) –