如何使用类自己的方法的返回类型定义类的内部别名?

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

提问人:Dmitry Kuzminov 提问时间:9/26/2023 最后编辑:Dmitry Kuzminov 更新时间:9/27/2023 访问量:119

问:

请考虑以下代码:

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;
}

我同时用于返回类型和函数的参数。但实际上,在此代码中,使用了唯一的类型,这就是返回的类型,因此我可以为该类型定义一个别名:autoLambdaWrapper::getLambda()useLambda()LambdaWrapper::getLambda()

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

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

现在,我希望使这个别名成为类的一部分:LambdaWrapper

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

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

但现在我得到了

错误:在扣除“auto”之前使用“auto LambdaWrapper::getLambda()”

如何在类中定义此别名?

更新:我仍然认为我的问题不需要任何额外的细节或澄清,但一些回复者不同意这一点。因此,让我补充更多细节。

我的问题从这里的问题开始:如何定义类型擦除的范围::视图?,我问的是如何类型擦除使用 lambda(可能捕获)的范围视图的复杂组合,以及如果添加或更改更多视图,此组合的类型可能会随着时间的推移而改变。我在以下策略中看到了该问题的解决方案之一:

  • 定义一个内联方法(在类的正文中),该方法返回视图的实际复杂组合,而不进行类型擦除。
  • 让函数减去实际类型。
  • 使用 定义该方法的返回类型的别名。decltype
  • 在使用该复杂视图组合的任何函数中使用该别名;别名会隐藏这种复杂性,即使视图会改变,消费者的代码也不会改变。

我可以通过将此别名定义为类范围之外的类型来做到这一点。我可以这样做,再创建一个间接级别(使用继承或嵌套类)。但是,如果没有像从人工类继承这样的黑客,我的目标就无法实现,这听起来不合逻辑。因此,提出这个问题的目的是找到最优雅的解决方案来实施上述策略。

使用 decltype C++ 别名

评论

1赞 Ted Lyngmo 9/26/2023
“仅使用类型” - 我没有看到实际使用情况。您是否需要在某个时候返回实际对象?如果在这种情况下编译为使用 lambda,为什么不定义一个内部类呢?getLambda()
1赞 Ted Lyngmo 9/26/2023
为什么不做一个内部类呢?这就是人们通常对需要专有名称的功能对象所做的。
1赞 NathanOliver 9/26/2023
问题在于,在类体中具有函数定义是Syntatic Sugar。该部件实际上不是类的一部分,因此在构造类定义时不可用。{ ... }
1赞 Ted Lyngmo 9/26/2023
对不起,我仍然无法“理解”为什么不能使用内部类。
1赞 n. m. could be an AI 9/26/2023
lambda 是一条红鲱鱼。 才是你不能做你想做的事的真正原因。 -- 不行。因为,嗯,这就是C++的工作方式。对不起。autoclass Foo { auto bar() { ... } using baz = decltype(...bar()...); };

答:

2赞 Jan Schultke 9/26/2023 #1

问题在于类是分两次通过解析的。第一次通过时,编译器会看到声明(而不是定义),但是,由于尚无可用的定义,因此解析失败。这就是为什么编译器在推导返回类型之前告诉您正在使用的原因。auto getLambda()using LambdaType = decltype(LambdaWrapper::get()->getLambda());getLambagetLambda()

如果您使用的是 C++20,则解决方案很简单。您可以将 lambda 的定义移动到 中,假设它没有捕获:decltype

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

请参阅编译器资源管理器中的实时示例

适用于旧标准的更通用的解决方案是将 lambda 表达式移动到单独的作用域中,例如基类:

// 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());
};

请参阅编译器资源管理器中的实时示例

您甚至可以变成 CRTP(奇怪的重复模板模式),以便您可以访问其中的成员函数。LambdaProviderLambdaWrapper

评论

0赞 Dmitry Kuzminov 9/26/2023
不幸的是,可能会被捕获。此外,从函数推导出类型至关重要:函数是定义复杂类型的函数(lambda 只是其中的一部分)。
0赞 Ted Lyngmo 9/26/2023
我认为一般的 CRTP 也会很困难。如果在 lambda 中捕获,则 lambda 不能与内部 .thisthisLambdaWrapper*using LambdaType = ...LambdaWrapper
1赞 Jan Schultke 9/26/2023
老实说,我需要更多细节才能提出最佳解决方案。OP 的示例过于小,并且包含没有捕获的 lambda。考虑到 OP 试图解决的实际问题,目前尚不清楚这个答案存在哪些问题。我不知道 CRTP 在这种情况下是否有效;这只是我抛出的一个想法,可能会奏效。
0赞 Ted Lyngmo 9/26/2023
我完全同意,我已经要求澄清真正需要做什么以及为什么他不能使用命名函子。这本来就是这么简单的例子
1赞 Red.Wave 9/26/2023
@DmitryKuzminov如果必须在函数内部定义返回类型,则该函数必须独立于别名的嵌套类型,在声明嵌套类型之前内联并完全定义。否则,您需要能够在函数外部定义类型。讨论结束。