【问题标题】:Passing overloaded CRTP class member method to lambda将重载的 CRTP 类成员方法传递给 lambda
【发布时间】:2020-12-05 08:43:41
【问题描述】:

考虑一下:

template<typename T>
struct base_t
{
    auto& f(int x) { return (T&)*this; }
    auto& f(char x) { return (T&)*this; }
};

struct derived_t : base_t<derived_t>
{

};

void test()
{
    derived_t instance;

    auto lambda = [&](derived_t&(derived_t::*g)(char))
    {
        (instance.*g)('a');

        //instance.f('a');
    };

    lambda(&derived_t::f);
}

如果不在该特定行 (//instance.f('a');) 中进行评论,我会收到以下错误 (MSVC 2019):

error C2664: 'void test::<lambda_1>::operator ()(derived_t &(__cdecl derived_t::* )(char)) const': cannot convert argument 1 from 'overloaded-function' to 'derived_t &(__cdecl derived_t::* )(char)'

当该行没有被注释掉时,它编译得很好。

为什么在lambda 中引用f 会神奇地允许编译器转换这个重载函数?

此外,没有 CRTP,这根本不会发生。

编辑:此外,正如@Jarod42 所指出的,

  • 明确返回类型 auto& -> T& 解决了这个问题。

  • 如果您使用命名函数而不是 lambda,问题就会消失。所以显然 lambda 和模板的交互是相关的。

【问题讨论】:

  • Clang 两者都接受,gcc 充当 msvc...Demo.
  • 明确返回类型 auto&amp; -> T&amp; Demo 解决了 gcc 和 msvc 的问题。
  • return *static_cast&lt;T*&gt;(this); 会影响结果吗?
  • 模板的成员只有在需要时才会被实例化,所以显式调用f会实例化它,如果你使用其他重载,gcc/msvc仍然会失败Demo。这可以解释 gcc/msvc 的行为。
  • @JaMiT 好吧,我不知道这是否是一个错误,甚至根本不知道它是什么。

标签: c++ lambda c++17 overloading crtp


【解决方案1】:

模板机制在使用类和函数时对其进行实例化。 使用相同的机制来评估关键字 auto 背后的类型。

在您的情况下,base_t&lt;T&gt;::f 函数的返回类型是 auto&amp;,并且需要计算函数调用。 因此,当您注释掉对它的唯一调用 (instance.f('a');) 时,无法计算函数的实际签名,并且编译器无法判断它是否可以转换为 derived_t&amp;(derived_t::*g)(char)

如果您将base_t&lt;T&gt;::f 函数定义如下,则可以注释掉instance.f('a');

template<typename T>
struct base_t
{
    T& f(int) { return *static_cast<T*>(this); }
    T& f(char) { return *static_cast<T*>(this); }
};

这里的类型是在专用类型 base_t&lt;derived_t&gt; 的实例化时推断出来的,而不是在调用 f 函数时推断出来的,因此编译器可以计算出它到函数类型 derived_t&amp;(derived_t::*g)(char) 的转换,而不必在你的代码。

【讨论】:

  • 我认为返回类型的计算需要实例化,而不是具体的函数调用。 an example of cppreference.com 表明获取函数模板的地址会导致计算返回类型。不过,这与&amp;derived_t::f 并不完美匹配;哪些差异消除了计算返回类型的要求?
  • 他们最接近的例子是template&lt;typename T&gt; auto g(T t) { return t; }。在那里,它是模板化的函数,因此它必然需要一个显式引用,例如调用,才能被实例化。在这个问题中,实例化的是类。我只是展开了我对模板推论的了解来得出我的结论,但我不知道标准对这个案例的真正含义。看起来 GCC 和 MSVC 采用了这种幼稚的方法,而 Clang 做了一些更聪明的事情。在标准指定这种情况之前,两者都可以。
  • 如果您在 lambda 中注释两行并在 f 中添加一些依赖名称垃圾(例如 T t; t.i;),您可以看到 clang 抱怨不存在的成员 i。因此,在lambda(&amp;derived_t::f); 行,它似乎决定实例化所有f 重载,而其他编译器仍在抱怨重载决议......
  • @VictorPaléologue 我正在查看的 cppreference 示例采用函数模板的地址,特别是 void g() { int (*p)(int*) = &amp;f; } // instantiates both fs to determine return types(其中 fs 是指定 auto 作为其返回类型的函数模板)。此代码无需函数调用即可触发模板实例化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多