我们如何使用一个概念来要求行为(例如是否存在某种方法)?
让我们举一个例子:
template <typename T>
concept HasFoo = requires(T t) {
t.foo();
};
现在我们有:
struct X {
void foo() {}
};
struct Y {};
static_assert(HasFoo<X>); // ok, passes
static_assert(!HasFoo<Y>); // ok, passes
如果我们加上:
template<typename T>
struct Z {
auto foo() {
return T().foo();
}
};
这与预期的一样:
static_assert(HasFoo<Z<X>>); // passes
但以下两种情况编译失败:
static_assert(HasFoo<Z<Y>>); // static assert fails
static_assert(!HasFoo<Z<Y>>); // compilation error: no member named 'foo' in 'Y'
当静态Assert可以通过时,我们会因为没有'foo'而得到编译错误,这并没有多大帮助。是否有一种方法来实现这个概念,使它在这个案例中起作用?
这是第一个问题,模板似乎是为了检查概念而示例化的,这使编译失败。
Code link
如果我们稍微改变Z:
template<typename T>
struct Z {
void foo() {
T().foo();
}
};
然后编译器认为Z有foo,不管它的内部类型是否实现了foo,因此:
static_assert(HasFoo<Z<Y>>); // passes
static_assert(!HasFoo<Z<Y>>); // fails
这是第二个问题。(似乎没有简单的解决办法)。
Code link
这两个问题是一样的吗?或者没有?有没有可能一个解决方案,而另一个没有?
如何安全地实现代码like this:
template<typename T>
void lets_foo(T t) {
if constexpr(HasFoo<T>) {
t.foo();
}
}
当模板化类型可能失败时:
int main() {
lets_foo(X{}); // ok, calls foo inside
lets_foo(Y{}); // ok, doesn't call foo inside
lets_foo(Z<X>{}); // ok, calls foo inside
lets_foo(Z<Y>{}); // fails to compile :(
}
注意:这个问题是一个基于类似但更具体的问题的后续,这个问题得到了具体的解决方案:How to do simple c++ concept has_eq - that works with std::pair (is std::pair operator== broken for C++20),看起来这个问题不仅仅是一个单一的问题。
3条答案
按热度按时间v2g6jxz61#
这里的主要问题是
Z
的foo()
没有任何约束,但它的实现期望表达式T().foo()
是良好格式的,这将在T
没有foo()
时在函数体内部导致硬错误,因为concept只检查函数的签名。最直接的方法是约束
Z::foo()
以符合其内部实现(尽管这也要求T
是默认可构造的)。qcuzuvrc2#
根据@PatrickRoberts的评论,我们不能SFINAE模板或检查函数的存在,其中存在依赖于模板的内部实现,这可能会导致编译错误,因为需要解析模板,这可能会遇到会导致硬错误的替换失败,或者是一个错误的答案--在问题结尾的
if constexpr
的意义上是错误的。看起来,唯一能把我们从上面拯救出来的是实现模板本身的人。
例如,我们可以如下实现Z:
现在我们可以安全地使用Z:
Code link
现在,如果我们想查询多个成员,会发生什么?我们也许应该使用Mixin,就像这样:
现在允许:
Code link
无论如何,负担都在模板实现者身上,假设他们知道这个需求。
在发布了我的答案之后,我现在看到了the solution proposed by @康桓瑋,我必须说它更简单和优雅(尽管Mixin方法在某些情况下可能有用)。
kxeu7u2r3#
C++不要求编译器在替换失败发生之前完全示例化所有代码。
这是因为编译器编写者认为这样做很难。
通常,方法的主体内的失败会导致硬编译时错误。在不修改方法本身的情况下,不存在检测此类故障的方法,并且这是有意的。在编译方法体时检测到错误,编译可以立即停止,没有回退。
当SFINAE(替换失败不是错误)是唯一的实现这种事情的技术时,没有失败硬的方法被称为“SFINAE友好”。
在概念时代,它看起来像:
而不是
预先概念,您可以进行手动检查,或使用宏,如:
然后做
它做了一些类似的事情。
这个问题-方法中的硬错误-发生在
std
库中。例如,vector
的operator<
被定义为在其包含的值上简单地调用<
-如果失败,则错误是硬错误。我在过去用per-std-container专门化来解决这个问题,它检测所包含类型的需求并(手动)推导容器属性。
避免这种手工工作的唯一方法是与你交互的类型进行合作。