简体   繁体   English

编译器无法处理std :: invoke

[英]Compiler cannot handle std::invoke

What is the problem with this? 这是什么问题?

struct foo {
    void process(int, char, bool) {}    
};

foo myfoo;

template <typename Method> struct thing {
    void doit() {
        Method m = Method{};
        (myfoo.*m)(5, 'a', true);
    }
};

int main() {
    thing<decltype(&foo::process)> t;
    t.doit();
}

I think this isolates the problem. 我认为这可以解决问题。 What is the workaround if I have to use the type Method , as in the case of my original post below? 如果我必须使用Method类型(如下面的原始帖子所述),该如何解决?

Original post: In the following attempted test: 原始帖子:在以下尝试的测试中:

struct Foo { int play (char, bool) {return 3;} };
struct Bar { double jump (int, short, float) {return 5.8;} };
struct Baz { char run (double) {return 'b';} };

int main() {
    Foo foo;  Bar bar;  Baz baz;
    Functor<decltype(&Foo::play), decltype(&Bar::jump), decltype(&Baz::run)> func;
    func(foo, bar, baz, 'c', true, 5, 2, 4.5, 6.8);
}

As you can predict, func is supposed to carry out 如您所料, func应该能够执行

foo.play('c', true);  bar.jump(5, 2, 4.5);  baz.run(6.8);

My implementation of the Functor class so far (ignoring perfect forwarding and such for now) is 到目前为止,我对Functor类的实现( Functor忽略完美的转发等)是

template <typename... Members>
struct Functor {
    using m = many_members<Members...>;
    template <typename... Args>
    typename m::return_types operator()(Args... args) const { // perfect forwarding to do later
        auto t = std::make_tuple(args...);
        auto objects = utilities::tuple_head<sizeof...(Members)>(t);
        auto arguments = utilities::extract_subtuple<sizeof...(Members), sizeof...(Args) - sizeof...(Members)>(t);
        call(objects, arguments);  // Won't compile on GCC 7.2 or clang 6.0.
    }
private:
    template <typename Tuple1, typename Tuple2>
    auto call (Tuple1& objects, const Tuple2& args) const {
        std::invoke(typename utilities::nth_element<0, Members...>::type{}, std::get<0>(objects), 'c', true);
    }
};

where my last line using std::invoke is just to test the concept before I continue. 我使用std::invoke最后一行只是在继续之前测试概念。 It however will not compile on either GCC 7.2 or clang 6.0, so I cannot continue with the generalization. 但是,它将无法在GCC 7.2或clang 6.0上进行编译,因此我无法继续进行概括。 Any workaround here, or a completely different implementation altogether? 这里有任何解决方法,还是完全不同的实现?

Here is everything I have so far: 这是我到目前为止所拥有的一切:

namespace utilities {
    template <std::size_t N, typename... Ts>
    struct nth_element : std::tuple_element<N, std::tuple<Ts...>> { };

    template <std::size_t Skip, std::size_t Take, typename Tuple>
    auto extract_subtuple (const Tuple&, std::enable_if_t<(Take == 0)>* = nullptr) {
        return std::tuple<>();
    }

    template <std::size_t Skip, std::size_t Take, typename Tuple>
    auto extract_subtuple (const Tuple& tuple, std::enable_if_t<(Take > 0)>* = nullptr) {
        return std::tuple_cat (std::make_tuple(std::get<Skip>(tuple)), extract_subtuple<Skip + 1, Take - 1>(tuple));
    }

    template <std::size_t N, typename Tuple>
    auto tuple_head (const Tuple& tuple) {
        return extract_subtuple<0, N>(tuple);   
    }
}

template <typename Rs, typename Ts, typename ArgsPacks, typename AllArgs, typename... Members> struct many_members_h;

template <typename Rs, typename Ts, typename ArgsPacks, typename AllArgs>
struct many_members_h<Rs, Ts, ArgsPacks, AllArgs> {
    using return_types = Rs;
    using classes = Ts;
    using args_packs = ArgsPacks;
    using all_args = AllArgs;
};

template <typename... Rs, typename... Ts, typename... ArgsPacks, typename... AllArgs, typename R, typename T, typename... Args, typename... Rest>
struct many_members_h<std::tuple<Rs...>, std::tuple<Ts...>, std::tuple<ArgsPacks...>, std::tuple<AllArgs...>, R(T::*)(Args...), Rest...> :
    many_members_h<std::tuple<Rs..., R>, std::tuple<Ts..., T>, std::tuple<ArgsPacks..., std::tuple<Args...>>, std::tuple<AllArgs..., Args...>, Rest...> { };

template <typename... Members>
struct many_members : many_members_h<std::tuple<>, std::tuple<>, std::tuple<>, std::tuple<>, Members...> { };

template <typename... Members>
struct Functor {
    using m = many_members<Members...>;
    template <typename... Args>
    typename m::return_types operator()(Args... args) const { // perfect forwarding to do later
        auto t = std::make_tuple(args...);
        auto objects = utilities::tuple_head<sizeof...(Members)>(t);
        auto arguments = utilities::extract_subtuple<sizeof...(Members), sizeof...(Args) - sizeof...(Members)>(t);
        call(objects, arguments);  // Won't compile on GCC 7.2 or clang 6.0.
    }
private:
    template <typename Tuple1, typename Tuple2>
    auto call (Tuple1& objects, const Tuple2& args) const {
        std::invoke(typename utilities::nth_element<0, Members...>::type{}, std::get<0>(objects), 'c', true);
    }
};

// Testing
#include <iostream>

struct Foo { int play (char, bool) {return 3;} };
struct Bar { double jump (int, short, float) {return 5.8;} };
struct Baz { char run (double) {return 'b';} };

int main() {
    Foo foo;  Bar bar;  Baz baz;
    Functor<decltype(&Foo::play), decltype(&Bar::jump), decltype(&Baz::run)> func;
    func(foo, bar, baz, 'c', true, 5, 2, 4.5, 6.8);
}

Taking your smaller first example, note that decltype(&foo::process) is the type called void (foo::*)(int, char, bool) . 以较小的第一个示例为例,请注意decltype(&foo::process)是称为void (foo::*)(int, char, bool)

This type does not contain or imply any association with the original function foo::process itself. 这种类型包含或暗示原有的功能有任何关联foo::process本身。 Just like the type int doesn't let you get the value of some particular int elsewhere in your program, or the type SomeClass doesn't let you refer to a SomeClass object elsewhere in your program, the type alone doesn't carry a value or identity. 就像类型int不允许您在程序的其他位置获取某个特定int的值,或者类型SomeClass不允许您在程序的其他位置引用某个SomeClass对象一样,仅类型不携带值或身份。

The expression Method{} value-initializes this pointer to member type. 表达式Method{}值初始化此指向成员类型的指针。 Which means the resulting value is a null pointer value. 这意味着结果值为空指针值。 Which means calling it is undefined behavior (and on many systems is likely to result in a segfault). 这意味着调用它是未定义的行为(在许多系统上可能会导致段错误)。

If you're using C++17 mode, you could use a template <auto Method> non-type parameter and simply pass &foo::process (without using decltype ) as the template argument. 如果您使用的是C ++ 17模式,则可以使用template <auto Method>非类型参数,只需将&foo::process (不使用decltype )作为模板参数传递。 Some SFINAE techniques could enforce that the argument is actually a pointer to member function, and some helper traits could be used to get the class type and parameter list tuple. 一些SFINAE技术可以强制将参数实际上是指向成员函数的指针,并且可以使用一些辅助特性来获取类类型和参数列表元组。

Or if you're using a standard earlier than C++17, you'll have to either make the function pointer a function argument, or make it a template parameter which follows the type, as in template <typename MethodType, MethodType Method> , then call as thing<decltype(&foo::process), &foo::process> . 或者,如果您使用的是C ++ 17之前的标准,则必须使函数指针成为函数参数,或者使其成为遵循该类型的模板参数,如template <typename MethodType, MethodType Method> ,然后调用thing<decltype(&foo::process), &foo::process>

Thanks to aschepler's answer and advice to use auto... instead of typename... for the member function pointers, I was able to carry the original goal: 感谢aschepler的回答和建议,将成员函数指针使用auto...而不是typename...作为实现,我得以实现最初的目标:

#include <tuple>
#include <functional>  // std::invoke
#include <type_traits>
#include <utility>

namespace utilities {
    template <std::size_t N, auto I, auto... Is>
    struct nth_element : nth_element<N - 1, Is...> { };

    template <auto I, auto... Is>
    struct nth_element<0, I, Is...> {
        static constexpr decltype(I) value = I;
    };

    template <std::size_t N, typename Pack> struct nth_index;

    template <std::size_t N, std::size_t... Is>
    struct nth_index<N, std::index_sequence<Is...>> : nth_element<N, Is...> { };

    template <std::size_t Skip, std::size_t Take, typename Tuple>
    auto extract_subtuple (const Tuple&, std::enable_if_t<(Take == 0)>* = nullptr) {
        return std::tuple<>();
    }

    template <std::size_t Skip, std::size_t Take, typename Tuple>
    auto extract_subtuple (const Tuple& tuple, std::enable_if_t<(Take > 0)>* = nullptr) {
        return std::tuple_cat (std::make_tuple(std::get<Skip>(tuple)), extract_subtuple<Skip + 1, Take - 1>(tuple));
    }

    template <std::size_t N, typename Tuple>
    auto tuple_head (const Tuple& tuple) {
        return extract_subtuple<0, N>(tuple);   
    }

    template <typename F, typename T, typename Tuple, std::size_t... Is>
    decltype(auto) invoke_with_tuple_h (F&& f, T&& t, Tuple&& tuple, std::index_sequence<Is...>&&) {
        return std::invoke(std::forward<F>(f), std::forward<T>(t), std::get<Is>(std::forward<Tuple>(tuple))...);
    }

    template <typename F, typename T, typename Tuple>
    decltype(auto) invoke_with_tuple (F&& f, T&& t, Tuple&& tuple) {
        return invoke_with_tuple_h (std::forward<F>(f), std::forward<T>(t), std::forward<Tuple>(tuple), std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{});
    }

    template <typename PartialSums, std::size_t Sum, std::size_t... Is> struct all_partial_sums_h;

    template <std::size_t... PartialSums, std::size_t Sum>
    struct all_partial_sums_h<std::index_sequence<PartialSums...>, Sum> {
        using type = std::index_sequence<PartialSums..., Sum>;
        using type_without_last_sum = std::index_sequence<PartialSums...>;  // We define this because this is what we need actually.
    };

    template <std::size_t... PartialSums, std::size_t Sum, std::size_t First, std::size_t... Rest>
    struct all_partial_sums_h<std::index_sequence<PartialSums...>, Sum, First, Rest...> :
        all_partial_sums_h<std::index_sequence<PartialSums..., Sum>, Sum + First, Rest...> { };

    template <typename Pack> struct all_partial_sums;

    template <std::size_t... Is>
    struct all_partial_sums<std::index_sequence<Is...>> : all_partial_sums_h<std::index_sequence<>, 0, Is...> { };

    template <typename Pack> struct pack_size;

    template <template <typename...> class P, typename... Ts>
    struct pack_size<P<Ts...>> : std::integral_constant<std::size_t, sizeof...(Ts)> { };

    template <typename PackOfPacks> struct get_pack_sizes;

    template <template <typename...> class P, typename... Packs>
    struct get_pack_sizes<P<Packs...>> {
        using type = std::index_sequence<pack_size<Packs>::value...>;
    };
}

template <typename Method> struct method_traits;

template <typename R, typename C, typename... Args>
struct method_traits<R(C::*)(Args...)> {
    using return_type = R;
    using class_type = C;
    using args_type = std::tuple<Args...>;  
};

template <typename Rs, typename Cs, typename ArgsPacks, auto... Members> struct many_members_h;

template <typename Rs, typename Cs, typename ArgsPacks>
struct many_members_h<Rs, Cs, ArgsPacks> {
    using return_types = Rs;
    using classes = Cs;
    using args_packs = ArgsPacks;
};

template <typename... Rs, typename... Cs, typename... ArgsPacks, auto F, auto... Rest>
struct many_members_h<std::tuple<Rs...>, std::tuple<Cs...>, std::tuple<ArgsPacks...>, F, Rest...> :
    many_members_h<std::tuple<Rs..., typename method_traits<decltype(F)>::return_type>, std::tuple<Cs..., typename method_traits<decltype(F)>::class_type>, std::tuple<ArgsPacks..., typename method_traits<decltype(F)>::args_type>, Rest...> { };

template <auto... Members>
struct many_members : many_members_h<std::tuple<>, std::tuple<>, std::tuple<>, Members...> { };

template <auto... Members>
struct Functor {
    using m = many_members<Members...>;
    using starting_points = typename utilities::all_partial_sums<typename utilities::get_pack_sizes<typename m::args_packs>::type>::type;

    template <typename... Args>
    typename m::return_types operator()(Args&&... args) const {
        constexpr std::size_t M = sizeof...(Members);
        auto t = std::make_tuple(std::forward<Args>(args)...);
        auto objects = utilities::tuple_head<M>(t);
        auto arguments = utilities::extract_subtuple<M, sizeof...(Args) - M>(t);
        return call(objects, arguments, std::make_index_sequence<M>{});
    }
private:
    template <typename Tuple1, typename Tuple2, std::size_t... Is>
    auto call (Tuple1& objects, const Tuple2& args, std::index_sequence<Is...>&&) const {  // perfect forwarding to do later
        return std::make_tuple(call_helper<Is>(objects, args)...);
    }
    template <std::size_t N, typename Tuple1, typename Tuple2>
    auto call_helper (Tuple1& objects, const Tuple2& args) const {  // perfect forwarding to do later
        constexpr std::size_t s = std::tuple_size_v<std::tuple_element_t<N, typename m::args_packs>>;;
        constexpr std::size_t a = utilities::nth_index<N, starting_points>::value;
        const auto args_tuple = utilities::extract_subtuple<a, s>(args);
        return utilities::invoke_with_tuple (utilities::nth_element<N, Members...>::value, std::get<N>(objects), args_tuple);
    }
};

// Testing
#include <iostream>

struct Foo { int play (char c, bool b) { std::cout << std::boolalpha << "Foo::play(" << c << ", " << b << ") called.\n";  return 3; }  };
struct Bar { double jump (int a, short b, float c) { std::cout << "Bar::jump(" << a << ", " << b << ", " << c << ") called.\n";  return 5.8; } };
struct Baz { char run (double d) { std::cout << "Baz::run(" << d << ") called.\n";  return 'b'; } };

int main() {
    Foo foo;  Bar bar;  Baz baz;
    Functor<&Foo::play, &Bar::jump, &Baz::run> func;
    const auto tuple = func(foo, bar, baz, 'c', true, 5, 2, 4.5, 6.8);
    std::cin.get();
}

Output: 输出:

Baz::run(6.8) called.
Bar::jump(5, 2, 4.5) called.
Foo::play(c, true) called.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM