SFINAE在类模板中使用typedef失败,引用另一个类模板中的typedef

Ose*_*Ose 1 c++ templates sfinae type-traits enable-if

我一直在研究一种产生关于在C++中包装其他类的类的编译时信息的方法.在我要问的问题的最小例子中,这样一个包装类:

  • 包含typedef WrappedType定义包装类的类型; 和
  • 重载一个被调用的结构模板IsWrapper,表明它是一个包装类.

有一个结构模板被调用WrapperTraits,然后可用于确定包装类型层次结构的(非包装)根类型.例如,如果包装类是一个被调用的类模板Wrapper<T>,那么根类型Wrapper<Wrapper<int>>就是int.

在下面的代码片段中,我实现了一个名为a的递归结构模板GetRootType<T>,它定义了一个typedef RootType包装类型的根类型T.WrapperTraitsjust 的给定定义包含定义的根类型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; 
}
Run Code Online (Sandbox Code Playgroud)

这正确编译(在这里尝试)并产生输出:

Wrapper<...<int>...>
int
Run Code Online (Sandbox Code Playgroud)

但是,我已经习惯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>
Run Code Online (Sandbox Code Playgroud)

我的问题是:关于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> {
};
Run Code Online (Sandbox Code Playgroud)

但是,如果任何人都可以看到书写的更优雅的方式fWrapperTraits,我会在看到很感兴趣!

Dan*_*rey 5

问题的第一部分是失败的原因.答案是在编译时级别,&&没有短路属性.这一行:

bool HasWrapper=IsWrapper<T>::value && IsWrapper<typename T::WrappedType>::value>
Run Code Online (Sandbox Code Playgroud)

失败,因为第一个条件是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; 
}
Run Code Online (Sandbox Code Playgroud)

实例


Cas*_*sey 5

您遇到的问题是由于 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;
};
Run Code Online (Sandbox Code Playgroud)

我可能会将整个特征系统编码为(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;
};
Run Code Online (Sandbox Code Playgroud)

虽然那里有很多机器,但它可能比您需要的更重。例如,WrapperTraits模板可能会被删除,以支持简单地使用WrappedTypeGetRootType直接。我无法想象你经常需要传递一个WrapperTraits实例化。