How to define an internal alias of the class using the return type of its own method?

132 Views Asked by At

Consider the code below:

struct LambdaWrapper {
    auto getLambda() {
        return []() {
            std::cout << "Lambda is called" << std::endl;
        };
    }
};

void useLambda(auto &&lambda) {
    lambda();
}

int main() {
    LambdaWrapper w;
    useLambda(w.getLambda());
    return 0;
}

I'm using auto for both LambdaWrapper::getLambda() return type and for the argument of the useLambda() function. But actually in this code the only type is used, and that is what LambdaWrapper::getLambda() returns, so I can define an alias for the type:

struct LambdaWrapper {
    auto getLambda() { ... }
    static LambdaWrapper* get();
};

using LambdaType = decltype(LambdaWrapper::get()->getLambda());
void useLambda(const LambdaType &lambda);

Now I wish to make this alias to be a part of the LambdaWrapper class:

struct LambdaWrapper {
    auto getLambda() { ... }
    static LambdaWrapper* get();
    using LambdaType = decltype(LambdaWrapper::get()->getLambda());
};

void useLambda(const LambdaWrapper::LambdaType &lambda);

But now I'm getting

error: use of ‘auto LambdaWrapper::getLambda()’ before deduction of ‘auto’

How could I define this alias inside the class?

Update: I still believe that my question does not need any additional details or clarity, but some responders disagree with that. So let me add more details.

My problem starts with the question here: How to define a type-erased ranges::view?, where I'm asking how to type-erase a complex composition of range-views that use lambdas (possibly capturing), and where the type of this composition may change over time if more views would be added or changed. I see one of the solutions of that problem in the following strategy:

  • Define an inline method (in the body of the class) that returns the actual complex composition of views with no type erasure.
  • Let the function deduct the actual type.
  • Define an alias for the return type of that method using decltype.
  • Use that alias in any function that consumes that complex composition of views; the alias would hide that complexity, and even if the views would change, the consumer's code wouldn't.

I could do that by defining this alias as a type outside of the scope of the class. I could do that creating one more level of indirection (employing either inheritance or a nested class). But it sounds illogical that my goal cannot be achieved without hacks like inheriting from an artificial class. So the purpose of asking this question is to find the most elegant solution to implement the strategy described above.

1

There are 1 best solutions below

6
Jan Schultke On

The issue is that classes are parsed in two passes. One the first pass, the compiler sees the declaration auto getLambda() (not a definition), and using LambdaType = decltype(LambdaWrapper::get()->getLambda());, however, since no definition of getLamba is available yet, parsing fails. This is why the compiler tells you that you are using getLambda() before the return type could be deduced.

If you are using C++20, the solution is trivial. You can just move the definition of your lambda into decltype, assuming that it has no captures:

struct LambdaWrapper {
    using LambdaType = decltype( []() {
            std::cout << "Lambda is called" << std::endl;
    });
    LambdaType getLambda() {
        return {};
    }
};

See live example at Compiler Explorer.

A more general solution which works with older standards is to move the lambda expression into a separate scope, such as a base class:

// note: You can also put getLambda() into a namespace, not into a class.
//       I suspect your example is overly minimal and you can't actually do that.
struct LambdaProvider {
    auto getLambda() {
        return []() {
            std::cout << "Lambda is called" << std::endl;
        };
    }
};

// At this point, LambdaProvider has been parsed completely, and we can
// obtain the type of getLambda().
struct LambdaWrapper : LambdaProvider {
    using LambdaType = decltype(std::declval<LambdaProvider>().getLambda());
};

See live example at Compiler Explorer.

You can even turn LambdaProvider into a CRTP (curiously recurring template pattern) so that you can access the member functions of LambdaWrapper inside of it.