C++ 泛型/模板编程最佳实践:限制类型,还是不限制类型

C++ 泛型/模板编程最佳实践:限制类型,还是不限制类型,c++,templates,boost,c++11,generic-programming,C++,Templates,Boost,C++11,Generic Programming,这是我的问题。我只是好奇,在限制可以传递给泛型函数或类的类型方面,大家的共识是什么。我想我在某一点上读到过,如果你在做泛型编程,一般来说,最好保持开放状态,而不是试图关闭它们(不要回忆起源代码) 我正在编写一个具有一些内部泛型函数的库,我觉得它们应该只允许库中的类型与它们一起使用,因为我的意思是使用它们。另一方面,我不确定我的努力是否值得 有人可能有关于这个话题的统计数据或权威评论的来源吗?我也对合理的意见感兴趣。希望这不会完全否定这个问题:\ 此外,这里是否有等同于“最佳实践”的标签?我并没有

这是我的问题。我只是好奇,在限制可以传递给泛型函数或类的类型方面,大家的共识是什么。我想我在某一点上读到过,如果你在做泛型编程,一般来说,最好保持开放状态,而不是试图关闭它们(不要回忆起源代码)

我正在编写一个具有一些内部泛型函数的库,我觉得它们应该只允许库中的类型与它们一起使用,因为我的意思是使用它们。另一方面,我不确定我的努力是否值得

有人可能有关于这个话题的统计数据或权威评论的来源吗?我也对合理的意见感兴趣。希望这不会完全否定这个问题:\

此外,这里是否有等同于“最佳实践”的标签?我并没有特别看到,但它似乎是有帮助的,能够提出一个给定的SO主题的所有最佳实践信息。。。也许不是,只是一个想法

编辑:到目前为止,有一个答案提到我正在做的库的类型将是重要的。它是一个数据库库,最终使用STL容器、变量(元组)、Boost Fusion等类似的东西。我知道这会有什么关系,但我也对决定走哪条路的经验法则感兴趣

始终尽可能保持打开状态-但一定要确保

  • 记录有效类型所需的接口和行为,以便与泛型代码一起使用
  • 使用类型的接口特征(traits)确定是否允许/不允许它。不要根据类型名称来决定
  • 如果出现以下情况,应做出合理的诊断: 有人用错了类型。C++ 模板在提升吨级方面非常出色 深度嵌套错误的数量(如果使用 错误类型-使用类型特征、静态断言和相关技术,可以轻松生成更简洁的错误消息

STL最强大的卖点之一是它的开放性,它的算法可以与我的数据结构以及它自己提供的数据结构协同工作,我的算法可以与它的数据结构协同工作,也可以与我的数据结构协同工作

让您的算法对所有类型都开放还是将它们限制在您的算法中,这在很大程度上取决于您正在编写的库,而我们对此一无所知


(最初我想回答的是,泛型编程的全部内容是完全开放的,但现在我发现泛型总是有限制的,你必须在某个地方划清界限。如果这有意义的话,也可以只限于你的类型。)

至少在我看来,正确的做法大致上是概念所尝试的:与其尝试验证您正在接收指定类型(或指定类型集之一),不如尽最大努力指定类型的要求,并验证您接收的类型具有正确的特征,并且可以满足您的模板要求


与概念非常相似,这样做的主要动机是在不满足这些需求时简单地提供好的、有用的错误消息。最终,如果有人试图在不满足其要求的类型上实例化模板,编译器将生成错误消息。问题是,除非您采取措施确保它是有用的,否则错误消息很可能不会有很大帮助。

在我的数据库框架中,我决定放弃模板而使用单个基类。泛型编程意味着可以使用任何或所有对象。特定类型类超过了少数泛型操作。例如,可以比较字符串和数字是否相等;BLOB(二进制大对象)可能需要使用不同的方法(例如比较存储在不同记录中的MD5校验和)

此外,字符串和数值类型之间还有一个继承分支


通过使用继承层次结构,我可以通过使用
字段
类引用任何字段,或者引用专门的类,例如
字段_Int

问题

如果您的客户端可以在公共头中看到您的内部函数,并且这些内部泛型函数的名称是“通用”的,那么您可能会使您的客户端面临意外调用您的内部泛型函数的风险

例如:

namespace Database
{

// internal API, not documented
template <class DatabaseItem>
void
store(DatabaseItem);
{
    // ...
}

struct SomeDataBaseType {};

}  // Database

namespace ClientCode
{

template <class T, class U>
struct base
{
};

// external API, documented
template <class T, class U>
void
store(base<T, U>)
{
    // ...
}

template <class T, class U>
struct derived
    : public base<T, U>
{
};

}  // ClientCode

int main()
{
    ClientCode::derived<int, Database::SomeDataBaseType> d;
    store(d);  // intended ClientCode::store
}
名称空间数据库
{
//内部API,未记录
模板
无效的
存储(数据库项);
{
// ...
}
结构SomeDataBaseType{};
}//数据库
命名空间客户端代码
{
模板
结构基
{
};
//外部API,有文档记录
模板
无效的
仓库(基地)
{
// ...
}
模板
结构派生
:公共基地
{
};
}//客户端代码
int main()
{
ClientCode::派生的d;
store(d);//预期客户代码::store
}
在本例中,
main
的作者甚至不知道数据库::存储存在。他打算调用ClientCode::store,却变得懒惰,让ADL选择函数,而不是指定
ClientCode::store
。毕竟,他对
store
的论点来自与
store
相同的名称空间,因此它应该可以正常工作

它不起作用。此示例调用
Database::store
。根据
Database::store
的内部结构,此调用可能会导致编译时错误,或者更糟糕的是,导致运行时错误

如何修复

对函数的命名越通用,发生这种情况的可能性就越大。为内部函数(必须出现在标题中的函数)指定真正的非泛型名称。或者将它们放在子命名空间中,如
details
。在后一种情况下,您必须确保您的客户端永远不会将
详细信息
作为用于ADL的关联命名空间。那是usua