【问题标题】:How to partially specialize function to invoke function with tuple elements as arguments如何部分专门化函数以调用以元组元素作为参数的函数
【发布时间】:2017-06-25 06:07:22
【问题描述】:

我正在尝试使用未打包的元组调用函数,但遇到了问题。我的代码依赖于在函数为零时对函数执行特殊情况的能力。此代码不起作用,但我将如何使用有效的 c++ 完成此操作?

//Initialize Recursion
template <class ret, class ... Ts>
ret templateHelpers::callFunctionFromTuple(ret (*function)(Ts...), std::tuple<Ts...> tuple) {
    return callFunctionFromTupleHelper<sizeof...(Ts), ret>(function, tuple);
}

//Recursively break down the tuple
template <int NumLeft, class ret, class ... ArgsF, class ... ArgsT, class ... Args>
ret templateHelpers::callFunctionFromTupleHelper(ret (*function)(ArgsF...), std::tuple<ArgsT...> tuple, Args... ts) {
    return callFunctionFromTuple<numLeft - 1, ret>(funcTs(function, std::tuple<Tuples...> tuple, std::get<NumLeft-1>(tuple) Ts... ts));
}

//Finally Call the Function
//TODO: fix the error. Partial specialization does not work, including that <0>
template <class ret, class ... ArgsF, class ... ArgsT, class ... Args>
ret templateHelpers::callFunctionFromTupleHelper<0>(ret (*function)(ArgsF...), std::tuple<ArgsT...> tuple, Args... ts) {
    return func(ts...);
}

PS 我用的是VS2017

【问题讨论】:

  • 我将如何做到这一点?
  • operator()写一个类模板?
  • @user3117152 - 查看我的回答。
  • 顺便说一句 std::apply 正是这样做的,如果有空,请尝试调查

标签: c++ templates variadic-templates variadic-functions


【解决方案1】:

C++ 中没有模板函数的部分特化。让您的函数遵循可以部分专门化的类模板。

namespace detail {
  template<...>
  struct callFunctionFromTupleImpl {
    static ret do_call(...) {
    }
  };

  // + more partial specializations
}

template <class ret, class ... Ts>
ret callFunctionFromTuple(ret (*function)(Ts...), std::tuple<Ts...> tuple) {
    return detail::callFunctionFromTupleHelperImpl<ret, Ts...>::do_call(function, tuple);
}

【讨论】:

    【解决方案2】:

    解压std::tuple 的更简单方法是使用sdt::index_sequence

    #include<utility>
    #include<tuple>
    
    template<typename Ret, typename... Ts, size_t... I>
    Ret callFunction_(Ret (*fn)(Ts...), std::tuple<Ts...> tup, std::index_sequence<I...>)
    {
        return fn(std::get<I>(tup)...);
    }
    
    template<typename Ret, typename... Ts, typename Tuple>
    Ret callFunction(Ret (*fn)(Ts...), Tuple&& tup)
    {
        return callFunction_(fn, std::forward<Tuple>(tup),
            std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>{}>{});
    }
    

    Live

    然后不需要委托给类模板:)

    【讨论】:

    • 很好的解决方案!只是它仅适用于右值元组参数,您可能希望将该元组参数模板化为完整类型模板。然后单独获取大小或使用make_index_sequence,如std::apply 实现中en.cppreference.com/w/cpp/utility/apply
    • @Curious 是的,这绝对是右值参考的一个糟糕之处,谢谢
    【解决方案3】:

    In addition to what @StoryTeller said,因为我写了一个完美转发的完整解决方案,却不知道这里已经发布了答案

    如果您不想考虑possible implementation from cppreference for std::apply 并修改它以适合您的程序。您需要将调用委托给结构

    #include <tuple>
    #include <cassert>
    
    namespace detail {
        template <int Size>
        struct InvokeHelper {
            template <typename Func, typename TupleType, typename... Args>
            static decltype(auto) apply(Func&& func, TupleType&& tup,
                                        Args&&... args) {
                constexpr auto tuple_size =
                    std::tuple_size<std::decay_t<TupleType>>::value;
                return InvokeHelper<Size - 1>::apply(
                        std::forward<Func>(func),
                        tup,
                        std::forward<Args>(args)...,
                        std::get<tuple_size - Size>(
                            std::forward<TupleType>(tup)));
            }
        };
        template <>
        struct InvokeHelper<0> {
            template <typename Func, typename TupleType, typename... Args>
            static decltype(auto) apply(Func&& func, TupleType&&, Args&&... args) {
                return std::forward<Func>(func)(std::forward<Args>(args)...);
            }
        };
    } // namespace detail
    
    template <typename Func, typename TupleType>
    decltype(auto) invoke_tuple(Func&& func, TupleType&& tup) {
        constexpr auto tuple_size = std::tuple_size<std::decay_t<TupleType>>::value;
        return detail::InvokeHelper<tuple_size>::apply(
                std::forward<Func>(func), std::forward<TupleType>(tup));
    }
    
    int main() {
        auto tup = std::make_tuple(1, 2);
        assert(invoke_tuple([](int a, int b) { return a + b; }, tup) == 3);
    }
    

    【讨论】:

      【解决方案4】:

      您还可以使用 std::enable_if 实现您想要的功能。如果有任何错别字,请提前道歉,因为接下来的几天我不会靠近电脑。

      template <std::size_t N, typename ret>
      typename std::enable_if<N != 0, ret>::type f
      (
      )
      {
          return f<N-1, ret>();
      }
      
      template <std::size_t N, typename ret>
      typename std::enable_if<N == 0, ret>::type f
      (
      )
      {
          return ret();
      }
      

      【讨论】:

      • 可能是由于格式错误且与问题无关。
      • 问题涉及通过函数模板的部分特化来结束递归。正如 StoryTeller 所写,这是不可能的。然而,这个问题有很多解决方案,我的解决方案绝对是其中之一。它出现在很多非常专业的等级代码中。格式不应该是一个因素,也不是我要在这里捍卫的东西。这是二十年经验的结晶。
      • 主要问题是这段代码没有“以元组元素作为参数调用函数”,而只是构造了一个 ret 类型的值,即它不是答案。它应该作为注释添加,或者必须以元组元素作为参数调用函数。没有人质疑enable_if 方法的有效性。
      • 首先,谢谢你的直接。但是提问者清楚地理解了这些细节,并且问题的字面意思是“我的代码依赖于在函数为零时对函数执行特殊情况的能力。这段代码不起作用,但我如何使用有效的 c++ 来完成这个?”这不正是我的回答所针对的吗?我相信我的回答是对实际问题的通用解决方案,而不是问题的标题。
      • 虽然提问者可能了解也可能不了解细节,但这是问答网站,应该有很多人可以找到并阅读问题和答案。假设所有读者都能找出将方法扩展到完整答案的方法是不安全的。这就是为什么不鼓励发布方法而不是答案的原因。随着时间的推移,这种部分答案往往会被严重否决。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-30
      • 2015-04-24
      • 2015-11-09
      • 1970-01-01
      • 2012-05-24
      相关资源
      最近更新 更多