C++模板:有条件启用的成员函数

ASt*_*oob 8 c++ templates enable-if c++11

我正在创建一个非常小的C++项目,我想根据自己的需要创建一个简单的矢量类.该std::vector模板类不会做.当向量类由chars(即vector<char>)组成时,我希望它能够与a进行比较std::string.经过一番乱搞之后,我编写的代码既可以编译也可以执行我想要的操作.见下文:

#include <string>
#include <stdlib.h>
#include <string.h>

template <typename ElementType>
class WorkingSimpleVector {
 public:
    const ElementType * elements_;
    size_t count_;

    // ...

    template <typename ET = ElementType>
    inline typename std::enable_if<std::is_same<ET, char>::value && std::is_same<ElementType, char>::value, bool>::type
    operator==(const std::string & other) const {
        if (count_ == other.length())
        {
            return memcmp(elements_, other.c_str(), other.length()) == 0;
        }
        return false;
    }
};

template <typename ElementType>
class NotWorkingSimpleVector {
 public:
    const ElementType * elements_;
    size_t count_;

    // ...

    inline typename std::enable_if<std::is_same<ElementType, char>::value, bool>::type
    operator==(const std::string & other) const {
        if (count_ == other.length())
        {
            return memcmp(elements_, other.c_str(), other.length()) == 0;
        }
        return false;
    }
};

int main(int argc, char ** argv) {
    // All of the following declarations are legal.
    WorkingSimpleVector<char> wsv;
    NotWorkingSimpleVector<char> nwsv;
    WorkingSimpleVector<int> wsv2;
    std::string s("abc");

    // But this one fails: error: no type named ‘type’ in ‘struct std::enable_if<false, bool>’
    NotWorkingSimpleVector<int> nwsv2;

    (wsv == s);  // LEGAL (wanted behaviour)
    (nwsv == s);  // LEGAL (wanted behaviour)
    // (wsv2 == s);  // ILLEGAL (wanted behaviour)
    // (nwsv2 == s);  // ??? (unwanted behaviour)
}
Run Code Online (Sandbox Code Playgroud)

我相信我理解为什么会发生错误:编译器为其创建类定义NotWorkingSimpleVector<int>,然后我的operator==函数的返回类型变为:

std::enable_if<std::is_same<int, char>::value, bool>::type
Run Code Online (Sandbox Code Playgroud)

然后变成:

std::enable_if<false, bool>::type
Run Code Online (Sandbox Code Playgroud)

然后产生错误:没有type成员std::enable_if<false, bool>,这确实是enable_if模板的整个点.

我有两个问题.

  1. 为什么SFINAE不会简单地禁用operator==for 的定义NotWorkingSimpleVector<int>,就像我想要的那样?这有兼容性的原因吗?是否还有其他用例我不知道; 对这种行为存在合理的反驳吗?
  2. 为什么第一个class(WorkingSimpleVector)有效?在我看来,编译器"保留判断":由于"ET"参数尚未定义,它放弃尝试判断是否operator==存在.我们是否依赖于编译器缺乏洞察力来允许这种有条件启用的功能(即使C++规范可以接受这种"缺乏洞察力")?

谢谢.


简单回答

这个答案与Yakk的答案相似,但并没有那么有用(Yakk的支持任意if-expressions).然而,它更简单,更容易理解.

#include <string>
#include <stdlib.h>
#include <string.h>

template <typename ElementType>
class WorkingSimpleVector {
 public:
    const ElementType * elements_;
    size_t count_;

    // ...

    template <typename ET = ElementType>
    inline typename std::enable_if<std::is_same<ET, char>::value && std::is_same<ElementType, char>::value, bool>::type
    operator==(const std::string & other) const {
        if (count_ == other.length())
        {
            return memcmp(elements_, other.c_str(), other.length()) == 0;
        }
        return false;
    }
};

template <typename ElementType>
class NotWorkingSimpleVector {
 public:
    const ElementType * elements_;
    size_t count_;

    // ...

    inline typename std::enable_if<std::is_same<ElementType, char>::value, bool>::type
    operator==(const std::string & other) const {
        if (count_ == other.length())
        {
            return memcmp(elements_, other.c_str(), other.length()) == 0;
        }
        return false;
    }
};

int main(int argc, char ** argv) {
    // All of the following declarations are legal.
    WorkingSimpleVector<char> wsv;
    NotWorkingSimpleVector<char> nwsv;
    WorkingSimpleVector<int> wsv2;
    std::string s("abc");

    // But this one fails: error: no type named ‘type’ in ‘struct std::enable_if<false, bool>’
    NotWorkingSimpleVector<int> nwsv2;

    (wsv == s);  // LEGAL (wanted behaviour)
    (nwsv == s);  // LEGAL (wanted behaviour)
    // (wsv2 == s);  // ILLEGAL (wanted behaviour)
    // (nwsv2 == s);  // ??? (unwanted behaviour)
}
Run Code Online (Sandbox Code Playgroud)

这通过利用我们想要的'if语句'的模板专门化来工作.我们将类作为基础std::vector,如果char值为vector<char>(第二个定义std::string),则只包含函数.否则,它根本没有功能(第一个定义NotWorkingSimpleVector<int>).该operator==参数使其成为"CRTP"(奇怪的重复模板模式).它允许模板通过使用该type函数来访问子类的字段.请记住,模板与任何其他类没有区别,因此它不能访问私有成员(除非子类将其声明为a std::enable_if<false, bool>).


修改版的Yakk的答案解释道

他/她声明的第一件事是帮助模板,它为我们完成了这整个条件声明:

std::enable_if<std::is_same<int, char>::value, bool>::type
Run Code Online (Sandbox Code Playgroud)

变量模板是可怕的,我认为在这种情况下可以删除它们.我们将其简化为:

std::enable_if<false, bool>::type
Run Code Online (Sandbox Code Playgroud)

enable_ifoperator==类型是一个空结构,如果条件NotWorkingSimpleVector<int>不是WorkingSimpleVector(见的第一个定义operator==).如果是,则std::vector类型是值char.我们的定义vector<char>只是std::stringNotWorkingSimpleVector<int>每次使用这个结构时都不需要我们写.

接下来,我们定义一个实现我们想要的行为的模板

#include <string>
#include <stdlib.h>
#include <string.h>

template <typename ElementType>
class WorkingSimpleVector {
 public:
    const ElementType * elements_;
    size_t count_;

    // ...

    template <typename ET = ElementType>
    inline typename std::enable_if<std::is_same<ET, char>::value && std::is_same<ElementType, char>::value, bool>::type
    operator==(const std::string & other) const {
        if (count_ == other.length())
        {
            return memcmp(elements_, other.c_str(), other.length()) == 0;
        }
        return false;
    }
};

template <typename ElementType>
class NotWorkingSimpleVector {
 public:
    const ElementType * elements_;
    size_t count_;

    // ...

    inline typename std::enable_if<std::is_same<ElementType, char>::value, bool>::type
    operator==(const std::string & other) const {
        if (count_ == other.length())
        {
            return memcmp(elements_, other.c_str(), other.length()) == 0;
        }
        return false;
    }
};

int main(int argc, char ** argv) {
    // All of the following declarations are legal.
    WorkingSimpleVector<char> wsv;
    NotWorkingSimpleVector<char> nwsv;
    WorkingSimpleVector<int> wsv2;
    std::string s("abc");

    // But this one fails: error: no type named ‘type’ in ‘struct std::enable_if<false, bool>’
    NotWorkingSimpleVector<int> nwsv2;

    (wsv == s);  // LEGAL (wanted behaviour)
    (nwsv == s);  // LEGAL (wanted behaviour)
    // (wsv2 == s);  // ILLEGAL (wanted behaviour)
    // (nwsv2 == s);  // ??? (unwanted behaviour)
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,operator==参数是给我们"CRTP"(奇怪的重复模板模式).有关其重要性的详细信息,请参阅上面的"简单答案".

接下来,type如果条件满足,我们声明只具有此函数的类型:

std::enable_if<std::is_same<int, char>::value, bool>::type
Run Code Online (Sandbox Code Playgroud)

所以,std::enable_if<false, bool>类型是:

  • 一个空结构时 enable_if
  • operator== 什么时候 NotWorkingSimpleVector<int>

最后,在完成所有这些之后,我们可以使用以下内容创建我们想要的类:

std::enable_if<false, bool>::type
Run Code Online (Sandbox Code Playgroud)

哪个按要求工作.

Yak*_*ont 5

SFINAE 在模板函数中工作。在模板类型替换的上下文中,替换的直接上下文中的替换失败不是错误,而是算作替换失败。

但请注意,必须有效的替换,否则您的程序格式不正确,无需诊断。我认为存在这种情况是为了将来可以向语言中添加进一步的“更具侵入性”或完整的检查,以检查模板函数的有效性。只要所述检查实际上检查模板是否可以用某种类型实例化,它就成为有效检查,但它可能会破坏期望没有有效替换的模板有效的代码(如果这是有意义的)。如果没有可以传递给让程序operator==编译的函数的模板类型,这可能会使您的原始解决方案成为格式错误的程序。

在第二种情况下,没有替换上下文,因此 SFINAE 不适用。失败是无可替代的。

最后我查看了传入的概念提案,您可以向模板对象中依赖于该对象的模板参数的方法添加 require 子句,并且在失败时,该方法将不会被考虑进行重载解析。这实际上就是您想要的。

在当前标准下,没有符合标准的方法可以做到这一点。第一次尝试是人们通常会做的尝试,它确实可以编译,但在技术上违反了标准(但不需要对故障进行诊断)。

我想出的符合标准的方法来做你想做的事:

如果条件失败,则将该方法的参数之一更改为对从未完成类型的引用。如果不调用,该方法的主体永远不会被实例化,并且该技术阻止它被调用。

使用 CRTP 基类帮助程序,该帮助程序使用 SFINAE 根据任意条件包含/排除方法。

template <class D, class ET, class=void>
struct equal_string_helper {};

template <class D, class ET>
struct equal_string_helper<D,ET,typename std::enable_if<std::is_same<ET, char>::value>::type> {
  D const* self() const { return static_cast<D const*>(this); }
  bool operator==(const std::string & other) const {
    if (self()->count_ == other.length())
    {
        return memcmp(self()->elements_, other.c_str(), other.length()) == 0;
    }
    return false;
  }
};
Run Code Online (Sandbox Code Playgroud)

我们这样做的地方:

template <typename ElementType>
class WorkingSimpleVector:equal_string_helper<WorkingSimpleVector,ElementType>
Run Code Online (Sandbox Code Playgroud)

如果我们选择,我们可以从 CRTP 实现中重构条件机制:

template<bool, template<class...>class X, class...>
struct conditional_apply_t {
  struct type {};
};

template<template<class...>class X, class...Ts>
struct conditional_apply_t<true, X, Ts...> {
  using type = X<Ts...>;
};
template<bool test, template<class...>class X, class...Ts>
using conditional_apply=typename conditional_apply_t<test, X, Ts...>::type;
Run Code Online (Sandbox Code Playgroud)

然后我们将没有条件代码的 CRTP 实现拆分出来:

template <class D>
struct equal_string_helper_t {
  D const* self() const { return static_cast<D const*>(this); }
  bool operator==(const std::string & other) const {
    if (self()->count_ == other.length())
    {
        return memcmp(self()->elements_, other.c_str(), other.length()) == 0;
    }
    return false;
  }
};
Run Code Online (Sandbox Code Playgroud)

然后将它们连接起来:

template<class D, class ET>
using equal_string_helper=conditional_apply<std::is_same<ET,char>::value, equal_string_helper_t, D>;
Run Code Online (Sandbox Code Playgroud)

我们使用它:

template <typename ElementType>
class WorkingSimpleVector: equal_string_helper<WorkingSimpleVector<ElementType>,ElementType>
Run Code Online (Sandbox Code Playgroud)

在使用时看起来相同。但背后的机制被重构了,那么,奖金呢?