https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119848
Bug ID: 119848
Summary: Concept check makes it impossible to return types that
depend on the type being defined
Product: gcc
Version: 14.2.1
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: jpakkane at gmail dot com
Target Milestone: ---
This might not be a bug in GCC itself, but in the C++ specification. The
behaviour is the same for Clang and MSVC. Still, this seems like a thing that a
developer might reasonably expect to work.
## Description
Assume that we have a non-templated string type called MyString and a template
container class MyVector<T>. The string class provides a split function.
Thus the relevant pieces of code would be like this:
class MyString {
...
MyVector<MyString> split() const;
};
This works fine. Now let's assume that MyVector has requirements on the types
it stores, for example that it can be noexcept-moved. If you specify them as
`static_assert`s in the MyVector class everything still works.
However if you specify them as a concept, then things fail. Assuming the
concept is named `WellBehaved` the output is:
<source>:30:5: error: constraints not satisfied for class template 'MyVector'
[with T = MyString]
30 | MyVector<MyString> split();
| ^~~~~~~~~~~~~~~
<source>:13:10: note: because 'MyString' does not satisfy 'WellBehaved'
13 | template<WellBehaved T>
However if you specify the return value as `auto` like this:
auto split() const { /* implementation here */ }
things work again.
## Full source code
https://gcc.godbolt.org/z/YjxWGdKbr
-------
#include<utility>
template<typename T>
concept WellBehaved = requires(T a, T b, const T &c, T &&d) {
requires noexcept(a = b);
requires noexcept(a = std::move(b));
requires noexcept(T{});
requires noexcept(T{b});
requires noexcept(T{c});
requires noexcept(T{d});
};
template<WellBehaved T>
struct Holder {
T foo;
};
struct Failing {
Failing() noexcept;
Failing(Failing &&o) noexcept;
Failing(const Failing &o) noexcept ;
Failing& operator=(Failing &&o) noexcept;
Failing& operator=(const Failing &o) noexcept ;
// The following line leads to
// a compile error because "Failing" is not
// fully defined yet.
//
// If you comment it out, everything compiles.
Holder<Failing> return_my_own_type_wrapped();
// If the return type is deduced, things work.
auto return_my_own_type_wrapped_deduced() {
return Holder<Failing> { *this };
};
};
Holder<Failing> instantiation;