许多函数的相同模板

Identical template for many functions

本文关键字:函数 许多      更新时间:2023-10-16

大家好(新年快乐!)

我正在用C++写一个(不是真的)简单的项目(我的第一个项目,来自纯C)。我想知道是否有一种方法可以简化具有相同模板模式的多个函数的定义。我认为用一个例子来解释这个问题会更好。

上下文

假设我有一个"Set"类,它表示一个数字列表,定义为

template <class T>
class Set {
static_assert(std::is_arithmetic<T>(), "Template argument must be an arithmetic type.");
T *_address;
...
}

因此,如果我有一个实例(比如Set<double>)和一个数组U array[N],其中U是另一种算术类型,N是整数,我希望能够执行一些操作,例如将数组的值分配给Set的值。因此,我在类中创建了函数模板

template <class U, int N>
void assign(U (&s)[N]) {
static_assert(std::is_arithmetic<U>(), "Template argument must be an arithmetic type.");
errlog(E_BAD_ARRAY_SIZE, N == _size);
idx_t i = 0;
do {
_address[i] = value[i];
} while (++i < size);
}

问题

就我的测试而言,上面的代码运行得非常好。然而,我发现这真的很难看,因为我需要static_assert来确保只有算术类型作为参数(参数U),并且我需要一种方法来确定数组大小(参数N)。此外,我还没有完成assign功能,但我需要很多其他功能,如addmultiplyscalar_product等。!

第一个解决方案

当时我想知道是否有一种更漂亮的方式来写这种课。经过一些工作,我提出了一个预处理器指令:

#define arithmetic_v(_U_, _N_, _DECL_, ...)                                             
template <class U, idx_t N> _DECL_                                                
{                                                                               
static_assert(std::is_arithmetic<U>(),"Rvalue is not an arithmetic type."); 
errlog(E_BAD_ARRAY_SIZE, N == _size);                                       
__VA_ARGS__                                                                 
}

从而将我的功能定义为

arithmetic_v(U, N,
void assign(U (&value)[N]),
idx_t i = 0;
do {
_address[i] = value[i];
} while (++i < _size);
)

这在某种程度上更干净,但仍然不是最好的,因为我被迫失去了包装函数主体的括号(必须在函数内部包含static_assert,才能使模板参数U在作用域中)。

问题

我发现的解决方案似乎运行得很好,代码比以前可读性更强,但是。。。难道我不能使用另一个结构来构建所有函数的更干净的定义,同时保留static_assert部分和关于数组大小的信息吗?为我需要的每个函数重复一次模板代码真的很难看。。。

谢谢

我只是想了解这门语言,因此任何关于这一论点的额外信息都将不胜感激。我已经尽我所能搜索了很多,但什么都找不到(也许我只是想不出合适的关键词来询问谷歌,以找到相关的东西)。提前感谢您的帮助,并祝大家新年快乐!

Gianluca

我强烈建议不要使用宏,除非没有办法绕过它们(我可以想到的一种情况是为了调试目的而获取行号)。来自谷歌C++风格指南(http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Preprocessor_Macros):

宏意味着您看到的代码与编译器看到的代码不同。这可能会引入意外行为,尤其是因为宏具有全局作用域。

我真的不明白你为什么要考虑使用static_assert丑陋。还有另一种方法可以确保模板仅专用于使用SFINAE的某些类型。

template <class T, class Enable = void>
class X;
template <class T>
class X<T, typename std::enable_if<std::is_integral<T>::value>::type> {
};

使用using语句(并非双关语),你可以做得更漂亮:

template <class T>
using enable_if_integral_t = typename std::enable_if<std::is_integral<T>::value>::type;
template <class T, class Enable = void>
class X;
template <class T>
class X<T, enable_if_integral_t<T>> {
};

现在

X<int> x; // ok, int is integral
X<float> y; // compile error

SFINAE(替换失败不是错误)是C++中的一个功能,在该功能中,如果模板专用化失败,则不会出现错误。

CCD_ 18。如果Cond为true,则类型T被启用为成员类型enable_if::type。否则,不定义enable_if::type。因此,对于浮点类型is_integral为false,而enable_if::type不存在,因此模板专用化

template <class T>
class X<T, typename std::enable_if<std::is_integral<T>::value>::type>

失败,但使用通用模板代替

template <class T, class Enable = void>
class X;

其被声明但未被定义。

这很有用,因为你可以有更多的专业化,比如:

template <class T>
using enable_if_integral_t = typename std::enable_if<std::is_integral<T>::value>::type;
template <class T>
using enable_if_floating_t = typename std::enable_if<std::is_floating_point<T>::value>::type;

template <class T, class Enable = void>
class X;
template <class T>
class X<T, enable_if_integral_t<T>> {
};
template <class T>
class X<T, enable_if_floating_t<T>> {
};

希望你觉得这至少很有趣。

新年快乐

编辑

在函数定义中,<T, enable_if_integral_t<T>>应该放在哪里?我只能用类模板来完成这项工作。。。

对于函数,enable_if::type可以是返回类型。例如,如果f返回int,则可以有:

#include <type_traits>
template <class T>
typename std::enable_if<std::is_integral<T>::value, int>::type f(T a) {
return 2 * a;
}
int main() {
f(3); // OK
f(3.4); // error
return 0;
}

using:

#include <type_traits>
template <class T, class Return = void>
using enable_if_integral_t = typename std::enable_if<std::is_integral<T>::value, Return>::type;
template <class T>
enable_if_integral_t<T, int> f(T a) {
return 2 * a;
}
int main() {
f(3); // OK
f(3.4); // Error
return 0;
}

我不明白为什么您认为static_assert或errlog语句如此丑陋,并怀疑它在一定程度上不熟悉该语言。尽管如此,您可以很容易地编写一个函数或宏(如果您想在assign等函数中使用__LINE__),将它们移出行,允许使用以下用法:

template <class U, int N>
void assign(U (&s)[N]) {
assert_array_n_numbers(s);
idx_t i = 0;
do {
_address[i] = s[i];
} while (++i < size);
}

难道我不能使用另一个构造来构建所有函数的更干净的定义,同时保留static_assert部分和关于数组大小的信息吗?为我需要的每个函数重复一次模板代码真的很难看。。。

的可能性而言-尽管IMHO可能不希望进行模糊处理-您可以让您的函数接受具有来自数组的模板化隐式构造函数的参数,在该构造函数中验证它的算术,然后使用它验证函数中的大小,允许使用以下用法:

template <typename U>
void assign(Arithmetic_Array<U>& s) {
assert_same_size(s);
idx_t i = 0;
do {
_address[i] = s[i];
} while (++i < size);
}

实施:

template <typename T>
class Arithmetic_Array
{
public:
template <size_t N>
Arithmetic_Array(T (&a)[N])
: p_(&a), size_(N)
{
static_assert(std::is_arithmetic<T>(),"Rvalue is not an arithmetic type.");
}
T& operator[](size_t i) { return p_[i]; }
const T& operator[](size_t i) const { return p_[i]; }
size_t size() const { return size_; }
private:
T* p_;
size_t size_;
};

讨论

"清洁剂"可能是主观的。特别是,您应该考虑使用直观类型C++源作为文档和可维护性的"正常"非宏的值。如果宏大大简化了许多函数,特别是如果它只在实现文件中使用,而不是在共享头中使用,那么这是值得的,但如果只有边际好处,那么就不值得混淆和去本地化。当你刚接触这种语言时,所有这些模板的东西可能看起来都很复杂和丑陋,但过了一段时间,它就会一目了然,并帮助读者了解函数的作用

在C++中,对模板的参数多态性采取"鸭子类型"的态度也是很常见的。这意味着你可以让人们传递任何类型的参数,如果这些类型支持模板实现尝试对它们进行的操作(即编译),那么希望这将是调用方想要的。这就是创建具有可预测语义行为的类型是一个好主意的原因之一,例如,仅当影响与内置类型或std::string上的相同运算符相似时才使用运算符重载。

不过,你想要的更严格的强制执行也有它的位置——Bjarne Stroustrup和其他人花了很多时间研究"概念",这是一种对用作模板参数的类型强制执行期望的机制,非常适合你在这里的"算术类型"规定。我希望它们能成为下一个C++标准。同时,静态断言是一个很好的方法。