C++14中的多态lambda函数,是否可以确定其参数类型和返回类型?

6

从这个问题开始(有没有办法找出Lambda的参数类型和返回类型?),我经常使用建议的 function_traits。然而,随着C++14的多态Lambda的到来,这给了我很大的困扰。

template <typename T>
struct function_traits
    : public function_traits<decltype(&T::operator())>
{};
// For generic types, directly use the result of the signature of its 'operator()'

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
// we specialize for pointers to member function
{
    enum { arity = sizeof...(Args) };
    // arity is the number of arguments.

    typedef ReturnType result_type;

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
        // the i-th argument is equivalent to the i-th tuple element of a tuple
        // composed of those arguments.
    };
};

如其他问题的答案所提出的operator(),现在已经被重载并按照标准支持以下功能:

auto lambda1 = [](auto& a) { a.foo(); } 

并且

auto lambda2 = [](auto&&... args) { foo(args...); };

现在这种重载会使function_traits类分崩离析,因为编译器无法解析operator()的正确版本。

lambda.cpp:98:38: error: reference to overloaded function could not be
      resolved; did you mean to call it?
      typedef function_traits<decltype(&T::operator())> caller;

在C++14中,是否有办法对多态lambda实现function_traits的功能?


4
在重载运算符时,您需要提供operator()的参数类型,以便能够知道其返回类型,因为每个重载可能会返回不同类型的值。请注意,这里只是翻译,没有解释或添加额外内容。 - Jarod42
我猜std::result_of也能获取到这些信息。可能需要与C++11版本进行更新,但它仍然可能有效。 - rubenvb
我的问题是我不知道lambda的确切类型。我通过可变模板template <typename L, typename...Ts> void foo(L lambda, Ts&&... args) { ... }获取lambda和传递的参数,因此我可能知道lambda的参数类型,但不知道它如何等待它们(作为引用或值)。 - Michael Haidl
1
decltype(lambda(std::forward<Ts>(args)...)) 是返回类型。获取参数类型需要另一个特性(需要编译器魔法),这在 C++14 中是不可用的。(它需要库基础1 TS中的调用类型特性)。 - T.C.
@T.C. 是否有任何编译器和STL实现已经支持这些特性? - Michael Haidl
我不知道有任何这样的东西。 - T.C.
2个回答

2

目前在普遍意义上无法实现,但如果您的lambda函数所有参数都使用auto关键字,并且可以使用某种已知类型替换它们中的所有参数,则可以修改此问题的答案: C++元函数以确定类型是否可调用

按以下方式进行修改:

static const bool OneArg = (sizeof( test<T, int>(0)  ) == 1);
static const bool TwoArg = (sizeof( test<T, int, int>(0)  ) == 1);
static const bool ThreeArg = (sizeof( test<T, int, int, int>(0)  ) == 1);
static constexpr std::size_t TemplatedOperatorArgSize = 
    OneArg
        ? 1
        : TwoArg
            ? 2
            : ThreeArg
                ? 3
                : -1;

对于嵌套的三元运算符表示歉意。然后使用类似以下的方式调用它:

template<size_t N, typename T>
struct Apply {
    template<typename F, typename... A>
    static inline decltype(auto) apply(F && f, A &&... a) {
        return Apply<N-1, T>::apply(::std::forward<F>(f), std::declval<T>(), ::std::forward<A>(a)...);
    }
};

template<typename T>
struct Apply<0, T> {
    template<typename F, typename... A>
    static inline decltype(auto) apply(F && f, A &&... a) {
        return invoke(std::forward<F>(f), std::forward<A>(a)...);
    }
};

template<std::size_t Size, typename T, typename F>
inline decltype(auto) apply(F && f) {
    return Apply<Size, T>::apply(::std::forward<F>(f));
}

使用 invoke 函数: http://en.cppreference.com/w/cpp/utility/functional/invoke

获取返回类型

using return_type = decltype(
    apply<has_callable_operator<T>::TemplatedOperatorArgSize, int>(function)
);

第一部分可以使用std::make_index_sequence来处理任意数量的参数进行重写。
但是一般来说,我会说你正在走进一个兔子洞。祝你好运。
注意:没有测试应用代码,但它应该足以让你开始。
编辑:此外,为了实现你想要的效果,返回类型需要独立于参数类型。

2

我在实现一个带有缓存获取函数的类似情况中遇到了问题。

template<class KEY, class VALUE, class FetchFunctor>
class Cache { ... };

第一阶段

我想要让用户省去声明他/她的FetchFunctor类型的需要,并且没有C++17类模板参数推导,所以我采用了像这样的createCache辅助方法:

// [1]
template<class KEY, class VALUE, class FetchFunctor>
auto createCache(FetchFunctor fetchFunctor) {
    return Cache<KEY, VALUE, FetchFunctor>(fetchFunctor);
}

因此,创建缓存非常容易,例如:
auto cache = createCache<int, int>([](int i){return i+3;});

第二阶段

让用户创建缓存更加方便,不需要提供缓存的类型,可以从提供的FetchFunctor中推断出两者。因此,我添加了一个额外的createCache方法:

// [2]
template<class FetchFunctor>
auto createCache(FetchFunctor fetchFunctor) {
    // function_traits is a namespace where I 'hide' the
    // traits structs for result type and arguments type deduction
    using f = function_traits::traits<decltype(fetchFunctor)>;
    using KEY = typename f::template arg<0>::type;
    using VALUE = typename f::result_type;
    return Cache<KEY, VALUE, FetchFunctor>(fetchFunctor);
}

现在创建缓存更加容易了:
auto cache = createCache([](int i){return i+3;});

那么,上面的两种'createCache'方法能共存吗?

幸运的是可以。

  1. 如果用户提供了模板参数Key,Value,则第一个方法是唯一匹配。
  2. 如果用户没有提供模板参数,则编译器认为第二个方法“更为专业化”并且会优先选择它。

这样我们就可以支持以下所有情况:

    // [a]
    auto cache = createCache([](int i){return i+3;});
    // compiler deduces Key, Value to be: int, int - using the 2nd createCache

    // [b]
    auto cache = createCache<int, int>([](auto i){return i+3;});
    // we have a generic lambda so we provide Key and Value - using the 1st createCache

    // [c]
    auto cache = createCache<string, string>(
        [](const char* s){return string(s) + '!';}
    );
    // we want different Key and/or Value than would be deduced - we use 1st createCache

然而...

以下内容无法编译:

    auto cache = createCache([](auto i){return i+3;});
    // we cannot deduce types for generic lambda
    // compiler goes to the 2nd createCache but Key and Value cannot be deduced
    // - compilation error

但这不应该困扰我们......您可以像上面 [b] 中提供键和值。


代码:

http://coliru.stacked-crooked.com/a/e19151a5c245d7c3


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接