如果constexpr和requires-expression用于临时概念检查

18
假设我们使用C++17的if constexpr和Concepts TS(例如,在最新的gcc版本中),我们想要检查模板函数中的类型是否有嵌套类型。
#include <iostream>

struct Foo { using Bar = int; };

template<typename T>
void doSmth(T)
{
    if constexpr (requires { typename T::Bar; })
        std::cout << "has nested! " << typename T::Bar {} << std::endl;
    else
        std::cout << "no nested!" << std::endl;
}

int main()
{
    doSmth(Foo {});
    //doSmth(0);
}

概念的文档资料很少,所以我可能理解错了,但似乎就是这样(而且有一个实时示例在Wandbox上)。
现在让我们考虑取消注释另一个doSmth调用时应该发生什么。合理的期望是要求子句将评估为false,并且if constexprelse分支将被执行。然而,与此相反,gcc将此视为严重错误。
prog.cc: In instantiation of 'void doSmth(T) [with T = int]':
prog.cc:17:13:   required from here
prog.cc:8:5: error: 'int' is not a class, struct, or union type
     if constexpr (requires { typename T::Bar; })
     ^~

这是gcc的一个bug,还是这是预期的行为?

请注意,使用单独的类(非原始类型)也会产生一个硬错误:https://wandbox.org/permlink/QIBdPOsxSVK5AD9r - Vittorio Romeo
2
据我所知,gcc的概念实现是基于Concepts TS的,该标准已经被接受并进行了一些修改,包括限制requires子句的使用范围。允许这样做已经在P0266中单独提出。我认为我们还无法回答你的问题。 - Rakete1111
@Rakete1111 嗯,类似于 requires { requires std::is_same_v<T, int> } 的语法在这个上下文中能够正常工作(我对这种语法还不是很熟悉,所以可能写错了)。 - 0xd34df00d
我的问题似乎与 https://stackoverflow.com/q/53493715/893406 相关,您的回答是这个硬错误是设计上的。 - v.oddou
它开始使用最新的GCC版本工作:https://wandbox.org/permlink/QIBdPOsxSVK5AD9r - Dmitry Sychov
3个回答

9

概念 问题3(“在更多上下文中允许requires-expression”)于6月份获得了WP状态。从目前的[expr.prim.req],特别是p6看来:

将模板参数替换为requires-expression可能导致其要求中形成无效类型或表达式,或违反这些要求的语义约束。在这种情况下,requires-expression评估为false;它不会导致程序无法形成。

我想说你的代码没问题,GCC没有正确实现问题3的解决方案。


感谢确认此语法正确!问题在于requires表达式内部的失败会导致完全的硬性失败,如果使用std::is_same_v编写类似要求TFoo的内容,则可以正常工作。不幸的是,我不太了解标准和已有的提案是否包含此内容,或者是否需要特别注意它。 - 0xd34df00d
@0xd34df00d 不好意思,我没有认真阅读问题。也许稍后我会看一下,但现在似乎这绝对不是一个问题(否则就违背了 requires 表达式本身的作用)。 - Columbo
@0xd34df00d 调整了答案。 - Columbo
太好了,非常感谢!我应该更仔细地阅读你最初提到的那段话。 - 0xd34df00d

6
这是使用概念if constexpr内进行检查类型是否具有作为模板参数提供的特定返回类型T的方法foo的工作示例:
template<class P, class T>
concept Fooable = requires(P p) {
    requires std::same_as<decltype(p.foo()), T>;
};

template<typename T>
void printIsFooable(const auto& p) {
    if constexpr( Fooable<decltype(p), T> ) {
        std::cout << "fooable <" << typeid(T).name() << ">" << std::endl;
    }
    else {
        std::cout << "not fooable <" << typeid(T).name() << ">" << std::endl;
    }
}

struct MyFoo {
    void foo() const {}
};

int main() {
    printIsFooable<void>(MyFoo{}); // fooable <v>
    printIsFooable<int>(MyFoo{});  // not fooable <i>
    printIsFooable<void>(int{});   // not fooable <v>
}

使用C++20编写的代码可以在GCC和Clang中编译


你不能把这个概念简单地写成这样吗:template<class P, class T> concept Fooable = std::same_as<decltype(P{}.foo()), T>; - 303
1
@303 你的版本要求P必须有一个空构造函数。 - Amir Kirsh
当认为有必要时,您仍然可以将其包装在 std :: declval 中,但我可以看到另一种选择变得不太吸引人。 - 303

4
从C++2a和gcc 10开始,它可以正常工作。
#include <iostream>

struct Foo { using Bar = char; };

template<typename T> void doSmth(T)
{
    if constexpr (requires { typename T::Bar; })
        std::cout << "T has Bar of type: " << typeid(typename T::Bar).name() << std::endl;
    else
        std::cout << "T does not have Bar" << std::endl;
}

int main()
{
    doSmth(Foo {});
    doSmth(1);
}

https://wandbox.org/permlink/qH34tI6oRJ3Ck7Mm


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接