【问题标题】:SFINAE failure with typedef in class template referring to typedef in another class templateSFINAE 失败,类模板中的 typedef 引用另一个类模板中的 typedef
【发布时间】:2015-03-17 14:24:00
【问题描述】:

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

  • 包含一个typedef WrappedType,定义了包装类的类型;和
  • 重载了一个名为IsWrapper 的结构模板,以表明它是一个包装类。

有一个名为WrapperTraits 的结构模板,然后可用于确定包装类型层次结构的(非包装)根类型。例如。如果包装类是名为Wrapper<T> 的类模板,则Wrapper<Wrapper<int>> 的根类型将是int

在下面的代码 sn-p 中,我实现了一个名为GetRootType<T> 的递归结构模板,它定义了一个typedef RootType,它给出了包装器类型T 的根类型。 WrapperTraits 的给定定义只包含GetRootType 定义的根类型,但实际上它会有一些额外的成员。为了测试它,我编写了一个普通函数f,它接受int,以及一个重载函数模板f,它接受一个以int 作为根类型的任意包装类。我使用 SFINAE 来区分它们,通过在函数模板的返回类型中使用std::enable_if 来检查f 的参数的根类型是否为int(如果f 的参数不是包装器,试图确定其根类型将失败)。在我问我的问题之前,这里是代码 sn-p:

#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> {
};

但是,如果有人能看到更优雅的写作方式fWrapperTraits,我会非常有兴趣看到它!

【问题讨论】:

    标签: c++ templates sfinae typetraits enable-if


    【解决方案1】:

    您问题的第一部分是为什么它会失败。答案是在编译时级别上,&amp;&amp; 没有短路属性。这一行:

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

    失败,因为第一个条件是false,但编译器尝试实例化第二部分,但Tint,因此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

    【讨论】:

    • 感谢您的回答!然而,我现在意识到我的问题对一些事情还不清楚——我会尽快更新它。关于第一部分,重点是我希望编译器尝试用int 替换T,意识到T::WrappedType 是无效表达式,然后在SFINAE 下拒绝替换。这似乎是当ROOT_TYPE_ACCESSORGetRootType 并且sn-p 编译时发生的情况。但是当ROOT_TYPE_ACCESSORWrapperTraits 时,这不会发生。我不清楚是什么让后一种情况如此不同。
    • 你对第二部分的回答确实让我很开心!在实践中,我有许多不相关的类,它们的行为类似于Wrapper,所以我希望像WrapperTraits&lt;WrapperA&lt;WrapperB&lt;WrapperC&lt;int&gt;&gt;&gt;&gt;::RootType 这样的表达式计算为int,但是您的解决方案可以通过简单地为每个Unwrap 专门针对每个@987654340 来扩展到这种情况。 @ 班级。我已经 +1 了你的答案,但没有接受它,因为它没有回答我的问题所问的主要问题(即第一部分)。
    • @Ose 很高兴你喜欢第二部分,而当我正要澄清第一部分时,Casey 已经回答了(为他 +1):)
    【解决方案2】:

    您遇到的问题是由于 SFINAE 仅发生在模板实例化的“直接上下文”(标准使用的术语,但没有很好地定义)中。 WrapperTraits&lt;int&gt; 的实例化auto f&lt;int&gt;() -&gt; ... 的实例化的直接上下文中,它成功了。不幸的是,WrapperTraits&lt;int&gt; 有一个格式不正确的成员RootType。该成员的实例化在直接上下文中不是,因此 SFINAE 不适用。

    要让这个 SFINAE 按您的意愿工作,您需要安排 WrapperTraits&lt;int&gt; 有一个名为 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 模板可能会被删除,而直接使用WrappedTypeGetRootType。我无法想象您经常需要传递 WrapperTraits 实例化。

    【讨论】:

    • 太好了,感谢您的回答和代码!是的,我在标准草案的第 14.8.2 节第 8 段中看到了有关“即时上下文”的内容,并想知道错误是否与此有关,但我并不完全清楚它们的含义“直接上下文”,所以无法确定这是否是原因。
    • 它确实说“诸如类模板特化的实例化之类的副作用......不在'即时上下文'中”,这可能意味着GetRootType&lt;int&gt;的实例化不在正如您的回答似乎暗示的那样,在评估f 的签名时,尝试替换T=int 的直接上下文,所以我会接受您的回答。不过,如果该标准对“直接上下文”给出了更清晰的定义,那将会很有帮助!
    • 假设包装器有一个基类,它是无模板的,包含纯虚拟方法,并且您只有指向其中两个对象的指针。你将如何测试它们包装的类型是否相同或者一个是另一个的基类(例如std::is_base_of)?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多