Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Clang++] What type alias should be selected when used together with "deducing this"? #128364

Open
BlankSpruce opened this issue Feb 22, 2025 · 0 comments
Labels
clang Clang issues not falling into any other category

Comments

@BlankSpruce
Copy link

I have an inheritance chain where derived class does something extra before delegating to base class implementation.

Here's an example implementing this kind of behaviour with virtual functions
#include <print>

struct Solver
{
    int state = 0;

    int step()
    {
        if (state++ == 2)
        {
            return state;
        }
        return solve();
    }

    virtual int solve()
    {
        return step();
    }
};

struct VerboseSolver : public Solver
{
    using Base = Solver;

    int solve() override
    {
        std::println("Current state: {}", state);
        return Base::solve();
    }
};

struct ExtraVerboseSolver : public VerboseSolver
{
    using Base = VerboseSolver;

    int solve() override
    {
        std::println("Remaining iterations (prediction): {}", 3 - state);
        return Base::solve();
    }
};

template <typename SolverT>
void example()
{
    SolverT solver{};

    auto result = solver.solve();
    std::println("Result: {}", result);
    std::println("");
}

int main()
{
    std::println("Example: Solver");
    example<Solver>();

    std::println("Example: VerboseSolver");
    example<VerboseSolver>();

    std::println("Example: ExtraVerboseSolver");
    example<ExtraVerboseSolver>();
}
Expected outcome:
Example: Solver
Result: 3

Example: VerboseSolver
Current state: 0
Current state: 1
Current state: 2
Result: 3

Example: ExtraVerboseSolver
Remaining iterations (prediction): 3
Current state: 0
Remaining iterations (prediction): 2
Current state: 1
Remaining iterations (prediction): 1
Current state: 2
Result: 3

Example on Godbolt. All three major compilers agree on the visible behaviour.

I've tried expressing this behaviour with "deducing this" feature to avoid virtual functions and maintain cooperative nature of all these classes.

Code:
#include <print>

struct Solver
{
    int state = 0;

    template <typename Self>
    int step(this Self&& self)
    {
        if (self.state++ == 2)
        {
            return self.state;
        }
        return self.solve();
    }

    template <typename Self>
    int solve(this Self&& self)
    {
        return self.step();
    }
};

struct VerboseSolver : public Solver
{
    using Base = Solver;

    template <typename Self>
    int solve(this Self&& self)
    {
        std::println("Current state: {}", self.state);
        return self.Base::solve();
    }
};

struct ExtraVerboseSolver : public VerboseSolver
{
    using Base = VerboseSolver;

    template <typename Self>
    int solve(this Self&& self)
    {
        std::println("Remaining iterations (prediction): {}", 3 - self.state);
        return self.Base::solve();
    }
};

template <typename SolverT>
void example()
{
    SolverT solver{};

    auto result = solver.solve();
    std::println("Result: {}", result);
    std::println("");
}

int main()
{
    std::println("Example: Solver");
    example<Solver>();

    std::println("Example: VerboseSolver");
    example<VerboseSolver>();

    std::println("Example: ExtraVerboseSolver");
    example<ExtraVerboseSolver>();
}

The issue arises when I want to access base class function by referring through type alias for that base class (self.Base::solve()). In ExtraVerboseSolver example it seems that Base type alias visible in VerboseSolver::solve is one defined in ExtraVerboseSolver which leads to infinite recursion VerboseSolver::solve -> VerboseSolver::solve. As far as I can guess it looks like that Base alias in VerboseSolver is shadowed by Base alias in ExtraVerboseSolver as if "deducing this" contributed to type alias resolution as well. It can be mitigated by using base class name instead of a type alias. However GCC doesn't require that mitigation and behaviour follows my virtual functions example. Example on Godbolt for all three major compilers with and without type alias: https://godbolt.org/z/4fYK8s344

Unfortunately I don't know what's standard compliant behaviour hence the question in the title. Is GCC right in this case?

@llvmbot llvmbot added the clang Clang issues not falling into any other category label Feb 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category
Projects
None yet
Development

No branches or pull requests

2 participants