C++模板:有条件启用的成员函数
C++ templates: conditionally enabled member function
我正在创建一个非常小的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
模板的全部要点。
我有两个问题。
- 为什么 SFINAE 不会像我希望的那样简单地禁用
NotWorkingSimpleVector<int>
的operator==
定义?这是否有一些兼容性原因?我是否缺少其他用例;这种行为是否存在合理的反驳? - 为什么头等舱(
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_t
的 type
类型是一个空结构,如果条件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_;
};
根据需要工作。
- 对RValue对象调用的LValue ref限定成员函数
- 为什么使用 "this" 指针调用派生成员函数?
- 将公共但非静态的成员函数与ALGLIB集成
- 使用指向成员的指针将成员函数作为参数传递
- 将重载的成员函数传递给函数模板
- 我不小心调用了一个没有自己类对象的成员函数.但这是怎么回事呢
- 如何在C++中使用非静态成员函数作为回调函数
- C++错误C2600:无法定义编译器生成的特殊成员函数(必须首先在类中声明)
- 关联容器的下界复杂性:成员函数与非成员函数
- 在 C++ 中用派生类型重写成员函数
- 链表的泛型函数remove()与成员函数remove)
- 如何将lambda作为模板类的成员函数参数
- constexpr构造函数需要常量成员函数时出现问题
- 将自由函数绑定为类成员函数
- 区分非成员函数和头文件中的成员函数
- 如何从子成员函数修改父公共成员变量
- 保留对其他类的成员函数的引用
- 在运算符重载定义中使用成员函数(const错误)
- 内联如何影响模块接口中的成员函数
- 将成员函数指针作为参数传递给模板方法