c++ 理解代码和错误消息

348 阅读2分钟

c++ 概念和需求在实践中的典型应用

为什么使用约束和需求是有用的,原因有很多。

  • 约束有助于理解模板上的限制,并在这些要求被打破时获得更容易理解的错误消息。

  • 当泛型代码没有意义时,约束可以用来禁用它:

  • 指定泛型类型可能会编译,但不会做正确的事情。我们可能需要修复重载解析,它决定调用多个可调用函数中的哪一个。

约束可用于重载或专门化泛型代码,以便针对不同类型的不同代码进行编译

理解代码和错误消息

假设我们想要编写将对象的值插入到集合中的泛型代码。多亏了模板,我们可以一次性实现它,一旦我们知道传递对象的类型,就可以编译这些类型的代码

template<typename Coll, typename T>

void add(Coll& coll, const T& val)

{

coll.push_back(val);

}

这段代码并不总是能够编译。对于实参的类型有一个隐藏的需求:

对于类型为Co11的容器,必须支持对类型为T的值的push back()调用。您也可以认为这是多个基本需求的组合

  • Type Co1l必须支持push back()。

  • 必须有从T型到Co11元素类型的转换。

  • 如果传递的参数的元素类型为Co11,则该类型必须支持复制(因为用传递的值初始化了一个新元素)。

如果这些需求中的任何一个被打破,代码将无法编译。例如:


std::vector<int> vec
   
add(vec,42);  //OK

add(vec,"hello"); //错误:没有从字符串转换为int

std::set<int> coll;

add(co11,42); // 错误:std::set<不支持push back()

std::vector<std::atomic<int>> aiVec;

std::atomic<int> ai{42};

add(aiVec,ai); //ERROR:cunnot copy/move atomics

当编译失败时,错误消息可能非常清晰,例如在模板的顶层没有找到push back()成员

image.png

然而,它们也很难阅读和理解。例如,当编译器必须处理不支持复制的坏需求时,问题在std::vector<的实现中被检测到。我们会得到40到90行错误消息,在这些错误消息中,您必须仔细地寻找中断的需求

image.png

您可能认为通过定义一个概念来检查是否可以执行push back()调用可以改善这种情况

template<typename Coll,typename T>

concept SupportsPushBack = requires(Coll c,T v) {

c.push_back(v);

};

但是,在std::vector<>的代码中仍然可以检测到可复制的检查(这次是在检查概念时,而不是在编译代码时)。

当我们指定push back()所使用的基本需求时,情况会变得更好

template<typename Coll,typename T>

requires std::convertible_to<T, typename Coll::value_type>

void add(Coll& coll, const T& val)

{

col1.push_back(val); 

}

这里,我们使用标准概念std::convertible to来确保传递的参数T的类型可以(隐式和显式)转换为集合的元素类型。

现在,如果需求被破坏了,我们应该得到一个错误消息,其中包含被破坏的概念和它被破坏的位置。例如:

image.png


开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情