【问题标题】:Compiler cannot handle std::invoke编译器无法处理 std::invoke
【发布时间】:2017-11-15 23:51:54
【问题描述】:

这有什么问题?

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

【问题讨论】:

  • "如你所料,func 应该执行" 为什么会这样做?
  • 错误是...?也许一个简化的程序证明错误可能是......?
  • clang 上的分段错误,GCC 上的内部编译器错误。您可以在没有 Functor 类的情况下隔离 call 函数,尽管语法会更难看。发生同样的错误。
  • @Barry 我想我隔离了这个问题,并在我的问题的顶部描述了它。我仍然不知道如何解决这个问题。
  • 我没有使用 clang++ 6.0.0 或使用 g++ 5.4.0 的 ICE 得到任何编译器段错误。尽管出于某种原因,Method m = Method{}; 上的 g++ 错误。如果我将其更改为Method m{};,那很好。 (在这两种情况下,程序在执行时都会出现段错误,正如预期的那样。)

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


【解决方案1】:

以较小的第一个示例为例,请注意 decltype(&amp;foo::process) 是称为 void (foo::*)(int, char, bool) 的类型。

此类型包含或暗示与原始函数foo::process 本身的任何关联。就像int 类型不允许您在程序的其他地方获取某个特定int 的值,或者SomeClass 类型不允许您在程序的其他地方引用SomeClass 对象一样,单独的类型不带有值或标识。

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

如果您使用 C++17 模式,则可以使用 template &lt;auto Method&gt; 非类型参数,并简单地将 &amp;foo::process(不使用 decltype)作为模板参数传递。一些 SFINAE 技术可以强制参数实际上是指向成员函数的指针,并且一些辅助特征可以用于获取类类型和参数列表元组。

或者,如果您使用的是早于 C++17 的标准,则必须将函数指针设为函数参数,或将其设为跟随类型的模板参数,如 template &lt;typename MethodType, MethodType Method&gt; 中所示,然后调用为thing&lt;decltype(&amp;foo::process), &amp;foo::process&gt;

【讨论】:

    【解决方案2】:

    感谢 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.
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-17
      • 2015-06-04
      • 1970-01-01
      • 2020-05-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多