0xd34df00d
0xd34df00d

Reputation: 1505

if constexpr and requires-expression for ad-hoc concepts checking

Let's say, given C++17's if constexpr and Concepts TS (for instance, in recent gcc versions), we'd like to check if a type in a template function has a nested type:

#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);
}

The documentation for concepts is scarce, so I might have got it wrong, but seems like that's it (and the live example is on Wandbox).

Now let's consider what should happen when uncommenting the other doSmth call. It seems reasonable to expect that the requires-clause would evaluate to false, and the else branch of the if constexpr will be taken. Contrary to that, gcc makes this a hard error:

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; })
     ^~

Is that a bug in gcc, or is that the intended behaviour?

Upvotes: 18

Views: 8714

Answers (3)

Dmitry Sychov
Dmitry Sychov

Reputation: 245

It works starting from C++2a and 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

Upvotes: 5

Amir Kirsh
Amir Kirsh

Reputation: 13752

Here is a working example of using concept inside if constexpr for checking if a type has the method foo with a specific return type T provided as a template parameter:

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>
}

Code compiles with C++20 in GCC and in Clang.

Upvotes: 6

Columbo
Columbo

Reputation: 60979

Concepts issue 3 ("Allow requires-expressions in more contexts") was given WP status in June. And judging by the current looks of [expr.prim.req], in particular p6:

The substitution of template arguments into a requires-expression may result in the formation of invalid types or expressions in its requirements or the violation of the semantic constraints of those requirements. In such cases, the requires-expression evaluates to false; it does not cause the program to be ill-formed.

I'd say your code is fine, and GCC hasn't implemented the resolution of issue 3 properly.

Upvotes: 9

Related Questions