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

C++ templates: conditionally enabled member function

本文关键字:成员 函数 启用 有条件 模板 C++      更新时间:2023-10-16

我正在创建一个非常小的C++项目,我想根据自己的需要创建一个简单的向量类。std::vector模板类将不起作用。当向量类由 char s 组成时(即 vector<char>(,我希望它能够与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)
}

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

std::enable_if<std::is_same<int, char>::value, bool>::type

然后变成:

std::enable_if<false, bool>::type

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

我有两个问题。

  1. 为什么 SFINAE 不会像我希望的那样简单地禁用NotWorkingSimpleVector<int>operator==定义?这是否有一些兼容性原因?我是否缺少其他用例;这种行为是否存在合理的反驳?
  2. 为什么头等舱(WorkingSimpleVector(有效?在我看来,编译器"保留判断":由于尚未定义"ET"参数,因此它放弃了尝试判断operator==是否存在。我们是否依靠编译器的"缺乏洞察力"来允许这种有条件启用的功能(即使这种"缺乏洞察力"在C++规范中是可以接受的(?

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

我们这样做的地方:

template <typename ElementType>
class WorkingSimpleVector:equal_string_helper<WorkingSimpleVector,ElementType>

如果我们

选择,我们可以重构 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;

然后我们拆分出没有条件代码的 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;
  }
};

然后将它们连接起来:

template<class D, class ET>
using equal_string_helper=conditional_apply<std::is_same<ET,char>::value, equal_string_helper_t, D>;

我们使用它:

template <typename ElementType>
class WorkingSimpleVector: equal_string_helper<WorkingSimpleVector<ElementType>,ElementType>

在使用点看起来相同。 但是背后的机器被重构了,所以,奖金?

模板

operator==基本上使它不可调用。您必须明确执行以下操作:

myvec.operator==<char>(str);

最简单的解决方案可能只是添加一个非成员函数:

bool operator==(const WorkingVector<char>& vec, const std::string& s);
bool operator==(const std::string& s, const WorkingVector<char>& vec);

要启用 SFINAE 并将其保留为成员函数,您必须将类型转发到其他内容:

bool operator==(const std::string& s) const {
    return is_equal<ElementType>(s);
}
template <typename T> // sure, T == ET, but you need it in this context
                      // in order for SFINAE to apply
typename std::enable_if<std::is_same<T, char>::value, bool>::type
is_equal(const std::string& s) {
    // stuff
}

简单的答案

这个答案与Yakk的

答案相似,但并不那么有用(Yakk的答案支持任意if表达式(。然而,它相当简单和容易理解。

template <typename ThisClass, typename ElementType>
class WorkingSimpleVector_Base {
};
template <typename ThisClass>
class WorkingSimpleVector_Base<ThisClass, char> {
private:
    ThisClass * me() { return static_cast<ThisClass*>(this); };
    const ThisClass * me() const { return static_cast<const ThisClass*>(this); };
public:
    bool operator==(const std::string & other) const {
        if (me()->count_ == other.length())
        {
            return memcmp(me()->elements_, other.c_str(), other.length()) == 0;
        }
        return false;
    }
};
template <typename ElementType>
class WorkingSimpleVector : public WorkingSimpleVector_Base<WorkingSimpleVector<ElementType>, ElementType> {
 public:
    const ElementType * elements_;
    size_t count_;
};

这是通过对我们想要的"if 语句"利用模板专用化来工作的。我们基于WorkingSimpleVector_Base类,然后仅包含ElementType值为char的函数(WorkingSimpleVector_Base的第二个定义(。否则,它根本没有任何功能(WorkingSimpleVector_Base的第一个定义(。ThisClass参数使其成为"CRTP"(奇怪的重复模板模式(。它允许模板通过使用 me() 函数访问子类的字段。请记住,该模板与任何其他类没有什么不同,因此它无法访问私有成员(除非子类将其声明为friend(。


雅克回答的修改版本解释

他/她声明的第一件事是一个帮助程序模板,它为我们执行整个条件声明:

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;

可变参数模板很可怕,我认为在这种情况下可以删除它们。让我们将其简化为:

template<bool, class X>
struct conditional_apply_t {
  struct type {};
};
template<class X>
struct conditional_apply_t<true, X> {
  using type = X;
};
template<bool test, class X>
using conditional_apply=typename conditional_apply_t<test, X>::type;

conditional_apply_ttype 类型是一个空结构,如果条件test不是true(参见conditional_apply_t的第一个定义(。如果为 true,则type类型为 X 的值。conditional_apply的定义只是消除了我们每次使用此构造时在conditional_apply_t<...>末尾编写::type的需要。

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

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

在这种情况下,D参数为我们提供了"CRTP"(奇怪的重复模板模式(。请参阅上面的"简单答案",了解有关为什么这很重要的更多详细信息。

接下来,我们声明一个类型,该类型仅在满足条件时具有此operator==函数:

template<class D, class ET>
using equal_string_helper=conditional_apply<std::is_same<ET,char>::value, equal_string_helper_t<D>>;

因此,equal_string_helper<D,ET>类型为:

  • ET != char时为空结构
  • equal_string_helper_t<D>ET == char

最后,在所有这些之后,我们可以使用以下方法创建我们想要的类:

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

根据需要工作。