Я работаю над созданием информации времени компиляции о классах, которые переносят другие классы на 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
, мне было бы очень интересно это посмотреть!
Отлично, спасибо за ваш ответ и код! Да, я видел немного о «непосредственном контексте» в разделе 14.8.2 параграфа 8 проекта стандарта и задался вопросом, может ли это быть связано с этой ошибкой, но мне было не совсем понятно, что они подразумевают под «непосредственный контекст», поэтому не мог точно сказать, было ли это причиной. – Ose
Он говорит, что «побочные эффекты, такие как создание специализированных шаблонов классов ... не находятся в« непосредственном контексте », хотя, по-видимому, это означает, что создание« GetRootType »не находится в непосредственном контексте попытка подстановки '' T = int'' при оценке '' '' '' '' '' '' '' '' '' '' '' '' '' '' подпись '', как ваш ответ, кажется, предлагает, так что я буду принимать ваш ответ. Было бы полезно, если бы стандарт дал более четкое определение «непосредственного контекста», хотя! –
Ose