【问题标题】:Detect if C++ lambda can be converted to function pointer检测 C++ lambda 是否可以转换为函数指针
【发布时间】:2015-11-08 14:41:58
【问题描述】:

我有一些代码可以为我正在研究的 JIT 想法生成程序集。我使用元编程通过分析函数类型来生成调用,然后生成正确的程序集来调用它。我最近想添加 lambda 支持,而 lambdas 有两个版本,非捕获(普通 __cdecl 函数调用)和捕获(__thiscall,以 lambda 对象为上下文的成员函数调用)。

__thiscall 稍微贵一些,所以我想尽可能避免使用它,并且我还想避免必须根据 lambda 类型使用不同的调用生成函数。

我尝试了很多方法来通过模板和 SFINAE 检测 lambda 类型,但都失败了。

非捕获 lambda 有一个 ::operator function_type* 可以用来将它们转换为函数指针,而捕获 lambda 则没有。

相关 C++ 规范:http://en.cppreference.com/w/cpp/language/lambda

有什么想法吗?

编辑 我想要一个适用于 vs 2013/2015、gcc 和 clang 的解决方案

测试代码如下

#include <utility>

    //this doesn't work
    template < class C, class T >
    struct HasConversion {
        static int test(decltype(std::declval<C>().operator T*, bool()) bar) {
            return 1;
        }

        static int test(...) {
            return 0;
        }
    };

    template <class C>
    void lambda_pointer(C lambda) {
        int(*function)() = lambda;

        printf("Lambda function: %p without context\n", function);
    }

    template <class C>
    void lambda_pointer_ctx(C lambda) {
        int(C::*function)() const = &C::operator();

        void* context = &lambda;

        printf("Lambda function: %p with context: %p\n", function, context);
    }

    int main() {
        int a;

        auto l1 = [] {
            return 5;
        };

        auto l2 = [a] {
            return a;
        };


        //non capturing case

        //works as expected
        lambda_pointer(l1);

        //works as expected (ctx is meaningless and not used)
        lambda_pointer_ctx(l1);



        //lambda with capture (needs context)

        //fails as expected
        lambda_pointer(l1);

        //works as expected (ctx is a pointer to class containing the captures)
        lambda_pointer_ctx(l1);

        /*
        //this doesn't work :<
        typedef int afunct() const;

        HasConversion<decltype(l1), afunct>::test(0);
        HasConversion<decltype(l2), afunct>::test(0);
        */


        return 0;
    }

【问题讨论】:

  • 你知道 lambda 的签名吗?如果你这样做,它会更干净一些。
  • 如果你知道签名,你可以利用std::is_assignable&lt;void(*&amp;)(int), decltype(lambda)&gt;{},或者,在你的情况下,std::is_assignable&lt;afunct*&amp;, decltype(lambda)&gt;{}typedef void afunct(int);
  • 不,这对任何签名都有效——我推断要从签名生成调用
  • 这很有趣,我确实推断出 lambda 的签名,它适用于所有情况
  • 在测试了目前为止的所有解决方案后,这是唯一适用于 vs2013 && 2015 的解决方案!试图看看我是否可以将它弯曲成通用的:D

标签: c++ templates c++11 lambda template-meta-programming


【解决方案1】:

如果您知道要将 lambda 转换为的函数的签名,则可以利用 std::is_assignable 特征:

auto lambda = [] (char, double) -> int { return 0; };
using signature = int(char, double);
static_assert(std::is_assignable<signature*&, decltype(lambda)>::value, "!");

DEMO

这样它也可以与通用 lambda 一起使用。


我想要一个适用于 vs 2013/2015、gcc 和 clang 的解决方案

如果您不知道签名,这里有一种方法可以替代检查 + 是否触发隐式转换。这个漏洞利用了std::is_assignable 测试并验证 lambda 是否可分配给与 lambda 的函数调用运算符具有相同签名的函数指针。 (就像使用一元运算符 plus 的测试一样,这不适用于泛型 lambda。但在 C++11 中没有泛型 lambda)。

#include <type_traits>

template <typename T>
struct identity { using type = T; };

template <typename...>
using void_t = void;

template <typename F>
struct call_operator;

template <typename C, typename R, typename... A>
struct call_operator<R(C::*)(A...)> : identity<R(A...)> {};

template <typename C, typename R, typename... A>
struct call_operator<R(C::*)(A...) const> : identity<R(A...)> {};

template <typename F>
using call_operator_t = typename call_operator<F>::type;

template <typename, typename = void_t<>>
struct is_convertible_to_function
    : std::false_type {};

template <typename L>
struct is_convertible_to_function<L, void_t<decltype(&L::operator())>>
    : std::is_assignable<call_operator_t<decltype(&L::operator())>*&, L> {};

测试:

int main()
{
    auto x = [] { return 5; };
    auto y = [x] { return x(); };

    static_assert(is_convertible_to_function<decltype(x)>::value, "!");
    static_assert(!is_convertible_to_function<decltype(y)>::value, "!");
}

GCCVC++Clang++

【讨论】:

  • 我最终使用了一个类似(但写得不太好)的解决方案。接受是因为更好的便携性。
【解决方案2】:

非捕获 lambda 有一个非常有趣的特性:它们可以转换为足够的函数指针,但是当您将一元 operator + 应用于它们时,它们也可以隐式地这样做。因此:

template<class...> using void_t = void;

template <class T, class = void>
struct has_capture : std::true_type {};

template <class T>
struct has_capture<T, void_t<decltype(+std::declval<T>())>> : std::false_type {};

int main() {
    auto f1 = []{};
    auto f2 = [&f1]{};

    static_assert(!has_capture<decltype(f1)>{}, "");
    static_assert( has_capture<decltype(f2)>{}, "");
}

【讨论】:

  • 令人讨厌的是,一个版本的 MSVC 打破了这一点:它提供了多个调用约定,而一元 + 变得模棱两可。 2015预发布可能吗?不知道它是否发货。
  • @Yakk 不知道,但如果这是真的,那就糟透了:/
  • 在VS 2013上确实坏了,安装2015进行测试
  • 2015 仍然存在歧义问题。在这两个编译器上,has_capture 总是返回 false。不过,在 GCC 5.1 上可以正常工作。知道 operator+ 记录在哪里吗?我在阅读规范时没有看到它...
  • @StefanosK.M.P.我认为this answer 涵盖了所有内容:)
【解决方案3】:

您采用的 HasConversion 方法是 C++03 的延续。想法是使用test 重载的不同返回类型(例如,一个返回char,另一个返回long)并检查返回类型的sizeof() 是否与您匹配期待。

不过,一旦我们使用 C++11,就会有更好的方法。例如,我们可以使用void_t:

template <typename... >
using void_t = void;

编写类型特征:

template <typename T, typename = void>
struct has_operator_plus : std::false_type { };

template <typename T>
struct has_operator_plus<T, 
    void_t<decltype(+std::declval<T>())>>
: std::true_type { };

int main() {
    auto x = []{ return 5; };
    auto y = [x]{ return x(); };

    std::cout << has_operator_plus<decltype(x)>::value << std::endl; // 1
    std::cout << has_operator_plus<decltype(y)>::value << std::endl; // 0
}

【讨论】:

  • 不能等到 C++17:template &lt;typename T&gt; concept bool has_operator_plus = requires (T t) {+t;};
  • 可悲的是,vs 2013(和 2015)似乎存在一些问题,使其始终返回 1。不过,在 gcc 5.2 上测试时它确实可以正常工作。感谢 void_t 技巧!我还没有真正达到 C++11 模式的水平,甚至我的 C++03 也很生疏。
  • 这很好,但在给定的上下文中也有点误导。据我所知(但如果我错了,请纠正我),lambda 没有定义一元加法运算符operator+。非捕获 lambda 简单地配备了到函数指针类型的隐式转换运算符,在请求需要标量类型的操作时调用该运算符。因此,在我看来,这个特征可能有一个更合适的名字。
猜你喜欢
  • 2020-11-22
  • 2013-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-27
  • 1970-01-01
  • 2018-10-29
  • 2018-06-15
相关资源
最近更新 更多