编译器无法处理std :: invoke

时间:2017-11-15 23:51:54

标签: c++ templates variadic-templates c++17 template-meta-programming

这有什么问题?

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

我认为这会隔离问题。如果我必须使用Method类型,那么解决方法是什么,如下面原始帖子的情况那样?

原帖: 在以下尝试的测试中:

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

正如您所预测的那样,func应该执行

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

到目前为止我对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);
    }
};

我使用std::invoke的最后一行只是为了在继续之前测试这个概念。但它不会在GCC 7.2或clang 6.0上编译,所以我不能继续进行泛化。这里有任何解决方法,或完全不同的实现吗?

到目前为止,这是我所拥有的一切:

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

2 个答案:

答案 0 :(得分:2)

采用较小的第一个示例,请注意decltype(&foo::process)类型为void (foo::*)(int, char, bool)

此类型包含或暗示与原始函数foo::process本身的任何关联。就像int类型不能让您在程序的其他地方获得某些特定int的值,或类型SomeClass不能让您参考SomeClass对象在您的程序中的其他位置,单独的类型不具有值或身份。

表达式Method{} value-将此指针初始化为成员类型。这意味着结果值是空指针值。这意味着调用它是未定义的行为(在许多系统上可能会导致段错误。)

如果你正在使用C ++ 17模式,你可以使用template <auto Method>非类型参数,只需传递&foo::process(不使用decltype)作为模板参数。一些SFINAE技术可以强制该参数实际上是指向成员函数的指针,并且可以使用一些辅助特征来获取类类型和参数列表元组。

或者,如果您使用的是早于C ++ 17的标准,则必须使函数指针成为函数参数,或者使其成为类型后面的模板参数,如{ {1}},然后调用template <typename MethodType, MethodType Method>

答案 1 :(得分:0)

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

输出:

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