18

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?

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
0xd34df00d
  • 1,496
  • 1
  • 8
  • 17
  • Note that using a separate class (non-primitive) also produces an hard error: https://wandbox.org/permlink/QIBdPOsxSVK5AD9r – Vittorio Romeo Nov 06 '17 at 19:34
  • 2
    AFAIK gcc's concepts implementation is based on Concepts TS, which has only been accepted with some modifications, including restricting where a `requires` clause is used. Allowing this has been proposed separately in [P0266](https://wg21.link/p0266). I don't think we can answer your question yet. – Rakete1111 Nov 06 '17 at 19:36
  • @Rakete1111 well, something along the lines of `requires { requires std::is_same_v }` works as expected (I'm still not that familiar with the syntax, so I might write it wrong) in this context – 0xd34df00d Nov 06 '17 at 19:40
  • My question seems related https://stackoverflow.com/q/53493715/893406 your answer is that this hard error is by design. – v.oddou Nov 27 '18 at 10:40
  • It started to work with the latest GCC version: https://wandbox.org/permlink/QIBdPOsxSVK5AD9r – Dmitry Sychov Dec 10 '19 at 19:23

3 Answers3

9

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.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • Thanks for confirming this syntax is fine! The problem is that the failure inside the `requires` expression causes a full hard failure, and if one writes something like requiring that `T` is `Foo` using `std::is_same_v`, it works just fine. Sadly, I don't know standardese enough to figure out if this follows from the standard and the proposals already, or whether special attention should be paid to it. – 0xd34df00d Nov 06 '17 at 20:52
  • @0xd34df00d My bad, I didn't read the question properly. I might have a look later, but for now it seems like that is certainly not a problem (would defeat the purpose of requires expressions themselves). – Columbo Nov 06 '17 at 20:55
  • @0xd34df00d Adjusted the answer. – Columbo Nov 09 '17 at 00:17
  • Great, thanks a lot! I should have read the paragraph you originally referred more thoroughly. – 0xd34df00d Nov 09 '17 at 00:22
6

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.

Amir Kirsh
  • 12,564
  • 41
  • 74
4

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

Johan
  • 74,508
  • 24
  • 191
  • 319
Dmitry Sychov
  • 237
  • 7
  • 16