我对在C ++中使用模板的正确/正确方法有疑问。
我们假设我们有一个这样的模板类:
template <typename T> class Foo
{
public:
T add(T n1, T n2);
};
template <typename T> T Foo<T>::add(T n1, T n2)
{
return(n1 + n2);
}
这对基本数据类型非常有用,比如int,double,float,char ......等。
Foo <int> foo_int;
std::cout << std::to_string(foo_int.add(2, 5)) << std::endl;
Foo <double> foo_double;
std::cout << std::to_string(foo_double.add(2.2, 6.1)) << std::endl;
Foo <char> foo_char;
std::cout << std::to_string(foo_char.add('A', 'm')) << std::endl;
但是对于复杂的数据类型,这不会很好,例如:
class Bar
{
public:
std::string bar;
};
Bar bar_1;
Bar bar_2;
Foo <Bar> foo_bar;
std::cout << std::to_string(foo_int.add(bar_1, bar_2)) << std::endl;
是否可以编写仅适用于少数数据类型的模板,或者仅在适用于所有类型的数据类型的情况下才使用模板,例如std :: vector。
如果编写仅适用于少数数据类型的模板是正确的,那么应该如何编写?对于类Foo
,我将知道Foo
仅适用于基本数据类型,但是我要将Foo
的代码提供给Alice,她并不知道。因此,如果她使用具有复杂数据类型的Foo
,她将收到编译器错误。
有没有告诉编译器/程序员模板只适用于某些数据类型,但编译器将抛出的错误除外。
答案 0 :(得分:4)
但是对于复杂的数据类型,这不会很好,例如:
这是因为Bar
没有提供Bar::operator+(Bar, Bar)
- 如果确实如此,那么它将适用于您的模板。
编写仅适用于少数数据类型
的模板是否可以
根据您的使用情况,是的。您通常希望支持符合特定概念的所有数据类型,但有时您可能还希望支持一组固定的类型。
有没有告诉编译器/程序员模板只适用于某些数据类型,但编译器将抛出的错误除外。
如果希望模板适用于符合特定接口的所有类型,则需要使用概念(可在C ++ 20中使用,可在C ++ 11中模拟)。
您可以使用检测习惯用法检查有关类型的属性,并使用static_assert
(或SFINAE)为您的班级用户提供编译时错误。
答案 1 :(得分:3)
编写仅适用于支持某些操作的类型的模板是完全可以的。事实上,相反的做法会使任何模板都成为英雄主义的壮举。
第一个错误保护是文档。例如,来自<algorithm>
的标准算法都接收任意类型的迭代器。你知道它们应该是迭代器,因为它们是以这种方式命名和记录的。作为迭代器意味着实例必须能够递增,比较和解除引用至少(但有些算法需要更多)。这组功能称为Iterator
概念,但这只是文档。
当编译器实例化模板并尝试编译您在用户提供的类型上执行的所有操作时,已经隐式执行了一个概念。但是,您可能希望更早,更清楚地报告错误,而不是使用指向您自己代码内部的十级深度错误堆栈。这可以通过两种主要方式完成:
硬错误,通过static_assert
。您为您的概念编写了编译时检查,通过static_assert
进行检查,如果失败则停止编译。
SFINAE,如果无法使用用户的对象,您可以设计代码,以便慷慨地走开。这对于函数非常有用,因为如果您的通用版本不适用,它会为其他重载留出空间。
这两种技术依赖于相对复杂的元编程技术,但在C ++库编写者中是众所周知的。
最后,我们希望将概念作为C ++ 20中的实际语言特性。然后,您将能够使用自然语法编写上述检查和错误报告。
但所有这些只是为了妥善处理失败案件。没有人希望编写适用于阳光下各种类型的模板。