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;

Reply via email to