【问题标题】:Type-traits experimentation, SFINAE seems not to be working, whines about the absence of something in the form of an error类型特征实验,SFINAE 似乎不起作用,以错误的形式抱怨缺少某些东西
【发布时间】:2013-10-03 05:29:22
【问题描述】:

我正在尝试使用 C++11,而不是将 C++ 用作 C++98(我来自 C),并且我现在已经达到了类型特征,而不是跳入我认为的标准' d 尝试解决问题。

通常我会使用继承来添加基于类型的方法,并依赖于用户,但我想使用特征,现在我不希望最终用户使用我的“自定义”,这是因为这是一个实验。

我首先创建了一个 True 和一个 False 类型,如下所示:

struct True {
    static const bool value = true;
};

struct False {
    static const bool value = false;
};

然后是我的enable_if 定义,我理解它使用结构或类既是结构(在 C 意义上,具有大小等)又是命名空间这一事实:

template<bool B,class T>
struct EnableIf {};

template<class T>
struct EnableIf<true,T> {
    typedef T type;
};

我现在希望只有EnableIftrue 有一个名为“type”的“命名空间类型成员”(如果我可以的话),无论模板T 是什么。这似乎有效。

我知道当我尝试访问 EnableIf&lt;false,T&gt;::type 时,由于缺少 type,错误/默认情况应该“静默失败”

我很确定到目前为止一切都是正确的,但我只是在冗长。

我的测试用例

我选择了一个列表作为我的测试场(再次不使用标准集,因为我正在调查)通常我使用类层次结构来执行此操作,我有一个 list,它只能采取行动作为一个数组,唯一的额外成员是int find(T*);,因为 T* 是 T 的标识。

然后我将其扩展为具有int find(T&amp;);int find(T&amp;,int)int count(T&amp;),这将使用 == 来比较 Ts。这就是我将其留给用户的意思,他们可以根据/他们/对类型的了解来选择他们想要的列表。

我想使用EnableIf(当我更有信心时使用std::enable_if)来代替,这样当模板被删除时,功能只有enabled,如果该类型能够被使用方式。

列表定义

template<class T>
class List {
public:
    typedef typename T::hasEquality hasEquality;
    virtual ~List() {}
    virtual T& operator[](int index) =0;
    virtual const T& operator[](int index) const =0;
    virtual void append(T*) =0;
    virtual int length() const =0;
    virtual
        typename EnableIf<hasEquality::value, bool>::type
    operator==(const List<T>& rhs) const {
        if(length() == rhs.length()) {
            for(int k=0;k!=length();k++) {
                if(!((*this)[k] == rhs[k])) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
        }
    }
    virtual
        typename EnableIf<T::hasEquality::value, int>::type
        count(const T& what) const =0;
};

这是一个列表而不是一组,所以顺序很重要。您可以看到,这应该使 hasEquality 具有传递性,因为:

如果 T 有相等的概念,那么 T 的列表也有相等的概念

然后我继续实现一个单链表。

测试类型

class A {
public:
    A(int value) { val = value; }
    typedef True hasEquality;
    bool operator==(const A& rhs) const {
        if(val == rhs.val) {
            return true;
        }
        return false;
    }

private:
    int val;
};

class B {
public:
    typedef False hasEquality;
};

结果

int main(int,char**) {
    LinkedList<A> listA;
    listA.append(new A(6));
    A a(6);
    std::cout<<"there are "<<listA.count(a)<<" of them\n";
    return 0;
}

正如您所料,这很有效。我的第一个测试最初包括 B,但这会导致问题。

int main(int,char**) {
    LinkedList<A> listA;
    listA.append(new A(6));
    A a(6);
    std::cout<<"there are "<<listA.count(a)<<" of them\n";

    LinkedList<B> listB;

    return 0;
}

这没有,它失败了:

src/main.cpp: In instantiation of ‘class List<B>’:
src/main.cpp:77:7:   required from ‘class LinkedList<B>’
src/main.cpp:176:16:   required from here
src/main.cpp:59:2: error: no type named ‘type’ in ‘struct EnableIf<false, bool>’
  operator==(const List<T>& rhs) const {
  ^
src/main.cpp:73:3: error: no type named ‘type’ in ‘struct EnableIf<false, int>’
   count(const T& what) const =0;
   ^
src/main.cpp: In instantiation of ‘class LinkedList<B>’:
src/main.cpp:176:16:   required from here
src/main.cpp:134:3: error: no type named ‘type’ in ‘struct EnableIf<false, int>’
   count(const T& what) const {
   ^
make: *** [build/main.o] Error 1

由于某种原因,它将错误标记放在 typename 行之后,在我使用 EnableIffalse 的任何地方都令人不快

我真的不知道这是为什么,没错,没有type,但这是设计使然!

研究

http://en.cppreference.com/w/cpp/types/enable_if

引用:

template<bool B, class T = void>
struct enable_if {};

template<class T>
struct enable_if<true, T> { typedef T type; };

我的只是名称不同,默认的 T 是无效的,将它添加到我的(如我所料)并不能解决问题。

http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/SFINAE

http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/enable-if

确认我的想法。

额外问题

constexpr 和静态

最初我尝试了struct False { constexpr bool operator==(bool what) { return !what; } };True 也是如此;

但这不起作用,我不能使用“静态”一词来限定 operator==,但我可以使用名为 constexpr static bool is(bool what); 的方法来达到相同的效果,为什么 constexpr 不暗示静态?

在我看来,constexprs 从来没有真正存在过,而且设计有点像 virtual 的反面,没有什么说你不能使用实例来调用静态方法,我刚刚检查了 C++03 标准,部分9.4确认了,这个有变化吗?

SFINAE

hasMember 尚未定义时,是否可以使用SFINAE 来假设False?我知道这不适用于基本类型等,这是一个实验。在我有信心之前,我不会在生产中使用这些技术。

【问题讨论】:

    标签: c++ c++11 metaprogramming


    【解决方案1】:

    问题出在这里:

    virtual
        typename EnableIf<T::hasEquality::value, int>::type
        count(const T& what) const =0;
    

    您遇到了另一个泛型编程(模板)和面向对象编程风格冲突的例子。

    SFINAE 是一种使用模板的元编程技术。尽管出现(使用T),但上面声明的函数不是模板。这是模板类中的一个普通函数。模板类型参数TList的参数,不是count的参数。

    例如,以下是 SFINAE 的示例:

    template<class T>
    class List {
    public:
    template<class T>
    class List {
    public:
        // ...
        template <typename U>
        typename EnableIf<std::is_same<U, T>::value && U::hasEquality::value, int>::type
        count(const U& what) const { std::cout << "1\n"; }
    
        template <typename U>
        typename EnableIf<std::is_same<U, T>::value && !U::hasEquality::value, int>::type
            count(const U& what) const { std::cout << "2\n"; }
    };
    
    };
    
    int main() {
        A a(1);
        B b;
        List<A> la;
        la.count(a); // outputs 1
        List<B> lb;
        lb.count(b); // ouputs 2
    }
    

    请注意,两个counts 现在是一个模板(在U 上参数化)。仅当TU 的类型相同时,两者才有效。这是一种仅接受T 的解决方法(它并不完美,例如,它会丢弃隐式转换)。此外,第一个要求U::hasEquality::value == true,第二个要求相反。

    这里的关键是 SFINAE 可以在模板上工作。

    但正如您所见,我更改了您的设计并使count 成为非虚拟的。不幸的是,您不能将上面的 count 函数设为虚拟,因为模板函数不能是虚拟的。

    基本问题如下。模板函数仅在调用时才被实例化。因此,当编译器解析List(我的版本)时,它还不知道count 的所有实例化将存在。

    对于每个虚拟函数,虚拟表中都应该有一个条目,当编译器解析List时,它必须知道虚拟表中有多少条目。

    因此,一方面,在解析List 时,编译器不知道模板实例的数量,另一方面,它必须知道虚函数的数量。结论是模板函数不能是虚函数。

    【讨论】:

      【解决方案2】:

      SFIANE 仅适用于模板参数推导和函数的重载解析。对于类,您可以改为使用模板专业化。在你的情况下,你可以做这样的模板专业化(不需要EnableIf):

      template <typename T, typename = typename T::hasEquality>
      class List;
      
      template <typename T>
      class List<T, False> {
          // no operator==
      };
      
      template <typename T>
      class List<T, True> {
      public:
          // ...
          bool operator==(const List<T,True>& rhs) const {
              // ...
          }
          // ...
      };
      

      至于您的constexpr 问题,您可以使用constexpr 构造函数来创建编译时对象,然后您可以调用constexpr 成员函数,该函数将在编译时运行,因此对@987654326 没有意义@ 表示static


      现在回答问题似乎为时已晚。您可以使用 SFINAE 有条件地启用成员函数,它必须是模板函数。所以你可以把你的operator==改成

      template <typename = typename EnableIf<T::hasEquality::value, void>::type>
      bool operator==(const List<T>& rhs) const {
          // ...
      }
      

      它应该可以工作。

      【讨论】:

      • 我可以避免复制和粘贴所有成员定义,但是声明呢?这会破坏 DRY 吗?您认为“enable_if”的名称有误导性吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-12
      • 1970-01-01
      相关资源
      最近更新 更多