3

I want to check that an attribute of a struct/class fits to my needs with concepts, but compiler complains about.

Example:

struct N
{
    char value;
    auto Get() { return value; }
};

struct M
{
    int value;
    auto Get() { return value; }
};

void func3( auto n )
    requires requires
{
    //{ n.Get() } -> std::same_as<int>;
    { n.value } -> std::same_as<int>;
}
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}


void func3( auto n )
    requires requires 
{
    //{ n.Get() } -> std::same_as<char>;
    { n.value } -> std::same_as<char>;
}
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

int main()
{
    M m;
    N n;

    func3( n );
    func3( m );
}

Results in a bit longer bunch of messages with ( gcc 10.1.1 )

main.cpp: In function 'int main()':
main.cpp:202:18: error: no matching function for call to 'func3(N&)'
  202 |         func3( n );
      |                  ^
main.cpp:154:10: note: candidate: 'void func3(auto:15) requires requires{{func3::n.value} -> decltype(auto) [requires std::same_as<<placeholder>, int>];} [with auto:15 = N]'
  154 |     void func3( auto n )
      |          ^~~~~
main.cpp:154:10: note: constraints not satisfied
main.cpp: In instantiation of 'void func3(auto:15) requires requires{{func3::n.value} -> decltype(auto) [requires std::same_as<<placeholder>, int>];} [with auto:15 = N]':
main.cpp:202:18:   required from here
main.cpp:154:10:   required by the constraints of 'template<class auto:15> void func3(auto:15) requires requires{{func3::n.value} -> decltype(auto) [requires std::same_as<<placeholder>, int>];}'
main.cpp:155:18:   in requirements  [with auto:15 = N]
main.cpp:158:13: note: 'n.value' does not satisfy return-type-requirement
  158 |         { n.value } -> std::same_as<int>;
      |           ~~^~~~~
cc1plus: note: set '-fconcepts-diagnostics-depth=' to at least 2 for more detail
main.cpp:165:10: note: candidate: 'void func3(auto:16) requires requires{{func3::n.value} -> decltype(auto) [requires std::same_as<<placeholder>, char>];} [with auto:16 = N]'
  165 |     void func3( auto n ) 
      |          ^~~~~
main.cpp:165:10: note: constraints not satisfied
main.cpp: In instantiation of 'void func3(auto:16) requires requires{{func3::n.value} -> decltype(auto) [requires std::same_as<<placeholder>, char>];} [with auto:16 = N]':
main.cpp:202:18:   required from here
main.cpp:165:10:   required by the constraints of 'template<class auto:16> void func3(auto:16) requires requires{{func3::n.value} -> decltype(auto) [requires std::same_as<<placeholder>, char>];}'
main.cpp:166:18:   in requirements  [with auto:16 = N]
main.cpp:169:13: note: 'n.value' does not satisfy return-type-requirement
  169 |         { n.value } -> std::same_as<char>;
      |           ~~^~~~~
main.cpp:203:18: error: no matching function for call to 'func3(M&)'
  203 |         func3( m );
      |                  ^   
main.cpp:154:10: note: candidate: 'void func3(auto:15) requires requires{{func3::n.value} -> decltype(auto) [requires std::same_as<<placeholder>, int>];} [with auto:15 = M]'
  154 |     void func3( auto n ) 
      |          ^~~~~
main.cpp:154:10: note: constraints not satisfied
main.cpp: In instantiation of 'void func3(auto:15) requires requires{{func3::n.value} -> decltype(auto) [requires std::same_as<<placeholder>, int>];} [with auto:15 = M]':
main.cpp:203:18:   required from here

The version with checking the return type of the Get() function works as expected. What is wrong here?

See on compiler explorer

  • clang: works as expected
  • gcc 10.1.1 fails with error message
  • gcc trunk: ICE! ubs :-

Update ( 12. Nov. 21 )

  • gcc trunk ( version 12.x.x.) works

It seems someone has fixed the bug: bug report

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • Um, why is `func3` defined twice? – StoryTeller - Unslander Monica Jul 08 '20 at 09:28
  • @StoryTeller-UnslanderMonica `func3` is an abbreviated function template, and the two definitions are using mutually exclusive constraint expressions, but I don't recognize the syntax from C++20 concepts, thus wondering if this is Concepts TS. – dfrib Jul 08 '20 at 09:30
  • @StoryTeller-UnslanderMonica Because it ones checks for int type once for char. It is only for academic purpose. I know all this can aslo be done by constexpr if and others and here especially with a simple function overload... – Klaus Jul 08 '20 at 09:30
  • Yeah, they looked virtually identical sans the `char` vs `int`. Got me confused. Valid overloads then. – StoryTeller - Unslander Monica Jul 08 '20 at 09:33
  • I'm pretty sure the `requires requires` syntax is Concepts TS and not Concepts C++20; citing [p0587r0: Concepts TS revisited](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0587r0.pdf): _"The resulting “`​requires requires`​” syntax is confusing and embarrassing."_. EDIT: [Or maybe they are indeed parts of C++20 Concepts](https://eel.is/c++draft/expr.prim.req#3.sentence-3). – dfrib Jul 08 '20 at 09:43
  • @dfri - They are indeed https://stackoverflow.com/questions/54200988/why-do-we-require-requires-requires – StoryTeller - Unslander Monica Jul 08 '20 at 09:44
  • @StoryTeller-UnslanderMonica That Q&A is a real gem that I had not seen before, thanks! – dfrib Jul 08 '20 at 09:47
  • The "requires requires" thing can simply removed from my example by writing a concept. But to simplify the code i write it as "ad hoc" requirement which is also mentioned in https://en.cppreference.com/w/cpp/language/constraints – Klaus Jul 08 '20 at 09:47
  • What's "ICE! ubs"? I get a seg fault on gcc trunk. should somebody submit a bug report? – JHBonarius Jul 09 '20 at 08:58
  • @JHBonarius: Copy/Paste error: simply the line before I use it. See last edit. Thanks – Klaus Jul 09 '20 at 09:01
  • 1
    @JHBonarius: Regarding ICE -> internal compiler error, yes filled a bug report https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96111 – Klaus Jul 09 '20 at 09:04

2 Answers2

5

GCC is actually right (when rejecting the code, not going bonkers). To quote the standard

[expr.prim.req.compound]/1.3

  • If the return-type-requirement is present, then:
    • Substitution of template arguments (if any) into the return-type-requirement is performed.
    • The immediately-declared constraint ([temp.param]) of the type-constraint for decltype((E)) shall be satisfied.

E is our expression, namely n.value.

Now, decltype(n.value) is char or int, that's because decltype has a special rule for class member access and id expressions. But decltype((n.value)) is char& or int&. The value category is encoded in the type of decltype when dealing with a general expression (such as a parenthesized class member access).

Your example works in GCC when we amend it

void func3( auto n )
    requires requires
{
    //{ n.Get() } -> std::same_as<int>;
    { n.value } -> std::same_as<int&>;
}
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}


void func3( auto n )
    requires requires 
{
    //{ n.Get() } -> std::same_as<char>;
    { n.value } -> std::same_as<char&>;
}
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    "decltype((E))" vs "decltype(E)" is really special :-) Thank you very much for that finding! – Klaus Jul 08 '20 at 10:16
0

As per [expr.prim.req.compound]/1.3

If the return-type-requirement is present, then:

  • Substitution of template arguments (if any) into the return-type-requirement is performed.

  • The immediately-declared constraint ([temp.param]) of the type-constraint for decltype((E)) shall be satisfied. [Example: Given concepts C and D,

    requires {
      { E1 } -> C;
      { E2 } -> D<A1, ⋯, An>;
    };
    

    is equivalent to

    requires {
      E1; requires C<decltype((E1))>;
      E2; requires D<decltype((E2)), A1, ⋯, An>;
    };
    

    (including in the case where n is zero). — end example ]

and, from [dcl.type.decltype]/1, particularly [dcl.type.decltype]/1.5 applies

For an expression E, the type denoted by decltype(E) is defined as follows:

  • [...]
  • (1.5) otherwise, if E is an lvalue, decltype(E) is T&, where T is the type of E;

Thus, as value is an lvalue in the { n.value } -> std::same_as<int>; expression, the type constraint on the return-type-requirement -> std::same_as<...>; for the { n.value } expression need to be matched for return type int&:

#include <iostream>
#include <type_traits>

template <typename T> struct Foo {
  T value;
  auto getValue() { return value; }
};

using FooInt = Foo<int>;
using FooChar = Foo<char>;

void abbreviatedFunctionTemplate(auto n) requires requires {
  { n.value } -> std::same_as<int&>; }
{ std::cout << "int overload\n" << std::endl; }

void abbreviatedFunctionTemplate(auto n) requires requires {
  { n.getValue() } ->std::same_as<char>; }
{ std::cout << "char overload\n" << std::endl; }

int main() {
  abbreviatedFunctionTemplate(FooInt{});  // int overload
  abbreviatedFunctionTemplate(FooChar{}); // char overload
}
dfrib
  • 70,367
  • 12
  • 127
  • 192