class 模板中的 typedef 引用另一个 class 模板中的 typedef 的 SFINAE 失败
SFINAE failure with typedef in class template referring to typedef in another class template
我一直在研究一种生成有关 classes 的编译时信息的方法,这些信息在 C++ 中包装了其他 classes。在我要问的问题的最小示例中,这样的包装器 class:
- 包含一个
typedef WrappedType
定义包装的类型class;和
- 重载名为
IsWrapper
的结构模板以表明它是一个包装器 class。
有一个名为 WrapperTraits
的结构模板,可用于确定包装类型层次结构的(非包装)根类型。例如。如果包装器 class 是一个名为 Wrapper<T>
的 class 模板,Wrapper<Wrapper<int>>
的根类型将为 int
.
在下面的代码片段中,我实现了一个名为 GetRootType<T>
的递归结构模板,它定义了一个 typedef RootType
,它给出了包装类型 T
的根类型。 WrapperTraits
的给定定义仅包含 GetRootType
定义的根类型,但实际上它会有一些额外的成员。为了测试它,我编写了一个接受 int
的普通函数 f
,以及一个接受具有 int
的任意包装器 class 的重载函数模板 f
] 作为其根类型。我使用 SFINAE 来区分它们,通过在函数模板的 return 类型中使用 std::enable_if
来检查 f
的参数的根类型是否为 int
(如果 f
's argument is not a wrapper, trying to determine its root type will fail).在我问我的问题之前,这里是代码片段:
#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
但是,我在 std::enable_if
的调用中使用了 GetRootType
来确定根类型。如果我改为使用 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
,我会很感兴趣的!
你问题的第一部分是失败的原因。答案是在编译时级别上,&&
没有短路属性。这一行:
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;
}
您遇到的问题是由于 SFINAE 只发生在模板实例化的 "immediate context"(标准使用的术语,但没有很好地定义)中。 WrapperTraits<int>
的实例化是 在 auto f<int>() -> ...
实例化的直接上下文中,它成功了。不幸的是,WrapperTraits<int>
有一个格式错误的成员 RootType
。该成员的实例化在直接上下文中是 not,因此 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
实例化。
我一直在研究一种生成有关 classes 的编译时信息的方法,这些信息在 C++ 中包装了其他 classes。在我要问的问题的最小示例中,这样的包装器 class:
- 包含一个
typedef WrappedType
定义包装的类型class;和 - 重载名为
IsWrapper
的结构模板以表明它是一个包装器 class。
有一个名为 WrapperTraits
的结构模板,可用于确定包装类型层次结构的(非包装)根类型。例如。如果包装器 class 是一个名为 Wrapper<T>
的 class 模板,Wrapper<Wrapper<int>>
的根类型将为 int
.
在下面的代码片段中,我实现了一个名为 GetRootType<T>
的递归结构模板,它定义了一个 typedef RootType
,它给出了包装类型 T
的根类型。 WrapperTraits
的给定定义仅包含 GetRootType
定义的根类型,但实际上它会有一些额外的成员。为了测试它,我编写了一个接受 int
的普通函数 f
,以及一个接受具有 int
的任意包装器 class 的重载函数模板 f
] 作为其根类型。我使用 SFINAE 来区分它们,通过在函数模板的 return 类型中使用 std::enable_if
来检查 f
的参数的根类型是否为 int
(如果 f
's argument is not a wrapper, trying to determine its root type will fail).在我问我的问题之前,这里是代码片段:
#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
但是,我在 std::enable_if
的调用中使用了 GetRootType
来确定根类型。如果我改为使用 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
,我会很感兴趣的!
你问题的第一部分是失败的原因。答案是在编译时级别上,&&
没有短路属性。这一行:
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;
}
您遇到的问题是由于 SFINAE 只发生在模板实例化的 "immediate context"(标准使用的术语,但没有很好地定义)中。 WrapperTraits<int>
的实例化是 在 auto f<int>() -> ...
实例化的直接上下文中,它成功了。不幸的是,WrapperTraits<int>
有一个格式错误的成员 RootType
。该成员的实例化在直接上下文中是 not,因此 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
实例化。