使用参数调用,将从单个参数

时间:2016-10-09 21:27:35

标签: c++ c++17 crtp

我的目标是创建一个可调用的(示例中为Derived),其中包含一个参数列表。它将使用单个参数调用,该参数用于解析参数列表的值。

我目前的尝试在结构上类似于一种绑定机制。它看起来像这样:

#include <string>
#include <utility>
#include <type_traits>

// this is a helper meta function
template<typename FunctionType, int ParameterCount> struct parameter_type;
template<typename Ret, typename FirstParam, typename ... MoreParams>
struct parameter_type<Ret(FirstParam, MoreParams...), 0> {
    using type = FirstParam;
};
template<typename Ret, int ParameterCount, typename FirstParam, typename ... MoreParams>
struct parameter_type<Ret(FirstParam, MoreParams...), ParameterCount>  {
    using type = typename parameter_type<Ret(MoreParams...), ParameterCount - 1>::type;
};


// here comes the base with CRTP to call the Derived operator()()
template<typename Derived, typename ... Params> struct Base;

template<typename Derived> struct Base<Derived> {};

template<typename Derived, typename FirstParam, typename ... Params>
struct Base<Derived, FirstParam, Params...> :
    public Base<Base<Derived, FirstParam, Params...>, Params...> {
    private:
        FirstParam first_param_;
    public:
        Base(const FirstParam& first_param, Params& ... params):
            Base<Base<Derived, FirstParam, Params...>, Params...>{params...},
            first_param_{first_param} {};

        template<typename PrefixParamT>
        int operator()(
            typename std::enable_if<std::is_convertible<PrefixParamT, 
                                                        typename parameter_type<Derived, 0>::type>::value,
                                    typename std::remove_reference<PrefixParamT>::type>::type&& prefix,
            Params ... params) {
            // actually we parse first_param from prefix here
            (*static_cast<Derived*>(this))(std::forward(prefix),
                                           first_param_,
                                           params...);
        }
};

// we use that to create various callables like this
struct Derived : public Base<Derived, int, const std::string&, double> {
    Derived(int a, const std::string& b, double c) :
        Base<Derived, int, const std::string&, double>{a, b, c} {};

    int operator()(const std::string& t, int a, const std::string& b, double c) {
        // her comes our business logic
    }

    // this is necessary to make the basic call operator available to 
    // the user of this class.
    int operator()(const std::string&);
};

// they should be usable like this
int main(int argc, char** argv) {
    Derived d{1, argv[0], 0.5};

    // call the most basic operator()(). 
    // here all the values from argv[1] should be parsed and converted
    // to parameters, that we pass to the most derived operator()()
    d(argv[1]);
}

当然,这不会编译,因为typename parameter_type<Derived, 0>::type>无法确定Derived是不完整的类型。我理解这一点,但我还没有设法提出替代实施。

如果当然我可以在不丢失任何功能的情况下省略对示例中的可转换性的检查,那么只需要清除编译器消息。在我的实际代码中,operator()()存在不同的重载,应根据Derived::operator()()的签名选择。因此,我需要这样的检查。

有不同的方法吗?我的目标是让像Derived这样的callables尽可能简单。我们将来会有很多不同的签名。这正是我为什么试图避免在prefix内解析Derived::operator()()的原因。

解决方案

为了这个问题的未来读者的利益。

感谢@Yakk提供的答案,我想出了一个解决方案。这仍然是示例代码,并且需要在parse_params_chooser<>模板中进行更复杂的类型特征检查以启用其他可调用函数,而不是自由函数。但我认为,这条道路现在铺平道路,应该就是这样。

#include <string>
#include <utility>
#include <tuple>
#include <experimental/tuple>
#include <type_traits>
#include <iostream>

// basic machinery
template <typename Derived, typename ResultType, typename ... Params> struct parse_params_t;

template<typename Derived, typename ResultType, typename FirstParam, typename ... Params>
struct parse_params_t<Derived, ResultType, FirstParam, Params...> :
    public parse_params_t<parse_params_t<Derived, ResultType, FirstParam, Params...>, ResultType, Params...> {
    private:
        typename std::remove_reference<FirstParam>::type first_param_;
    public:
        parse_params_t(const typename std::remove_reference<FirstParam>::type& first_param, Params&& ... params):
            parse_params_t<parse_params_t<Derived, ResultType, FirstParam, Params...>, ResultType, Params...>{std::forward<Params>(params)...},
            first_param_{first_param} {};

        using parse_params_t<parse_params_t<Derived, ResultType, FirstParam, Params...>, ResultType, Params...>::parse;

        template<typename PrefixParamT>
        auto parse(const PrefixParamT& prefix, const Params& ... params) -> ResultType {
            return static_cast<Derived*>(this)->parse(prefix, first_param_, params...);
        }
};

template<typename Derived, typename ResultType, typename LastParam>
struct parse_params_t<Derived, ResultType, LastParam> {
    private:
        LastParam last_param_;
    public:
        parse_params_t(const LastParam& last_param):
            last_param_{last_param} {};

        template<typename PrefixParamT>
        auto parse(PrefixParamT&& prefix) -> ResultType {
            return static_cast<Derived*>(this)->parse(std::forward<PrefixParamT>(prefix), last_param_);
        }
};


// put things together in a last derived type
template <typename ResultType, typename ... Params>
struct parse_params_helper : public parse_params_t<parse_params_helper<ResultType, Params...>, ResultType, Params...> {
    parse_params_helper(Params&& ... params):
        parse_params_t<parse_params_helper<ResultType, Params...>, ResultType, Params...>{std::forward<Params>(params)...} {};

    using parse_params_t<parse_params_helper<ResultType, Params...>, ResultType, Params...>::parse;

    template<typename PrefixParamT>
    auto parse(const PrefixParamT& prefix, const Params& ... params) -> ResultType {
        return {params...};
    }
};


// choose parser depending on handler parameter types.
template <typename PrefixParamT, typename Handler> struct parse_params_chooser;

template <typename PrefixParamT, typename ... Params>
struct parse_params_chooser<PrefixParamT, int(Params...)> {
    static auto parse(int (handler)(Params...), Params&& ... params) {
        return [helper = parse_params_helper<std::tuple<Params...>, Params...>{std::forward<Params>(params)...},
                handler](PrefixParamT&& prefix) mutable -> int {
            return std::experimental::apply(handler, std::tuple_cat(helper.parse(prefix)));
        };
    }
};

template <typename PrefixParamT, typename ... Params>
struct parse_params_chooser<PrefixParamT, int(PrefixParamT, Params...)> {
    static auto parse(int (handler)(PrefixParamT, Params...), Params&& ... params) {
        return [helper = parse_params_helper<std::tuple<Params...>, Params...>{std::forward<Params>(params)...},
                handler](PrefixParamT&& prefix) mutable -> int {
            return std::experimental::apply(handler, std::tuple_cat(std::make_tuple(prefix), helper.parse(prefix)));
        };
    }
};

// create a nice free function interface to trigger the meta programm
template <typename PrefixParamT, typename Handler, typename ... Params>
auto parse_params(Handler& handler, Params&& ... params) {
    return parse_params_chooser<PrefixParamT, Handler>::parse(handler, std::forward<Params>(params)...);
}


// now we can use that to create various callables like this
auto handler(std::string t, int a, std::string b, double c) -> int {
    // her comes our business logic
    std::cout << "handler: " << t << " " << a << " " << b << " " << c << std::endl;
}

auto other_handler(int a, std::string b, double c) -> int {
    // more business logic
    std::cout << "other_handler: " << a << " " << b << " " << c << std::endl;
}

// they should be usable like this
auto main(int argc, char** argv) -> int {
    auto h = parse_params<std::string>(handler, 1, argv[0], 0.5);
    auto g = parse_params<std::string>(other_handler, 2, std::string(argv[0]) + " blabla", 1.5);

    // call the lambda, that will parse argv[1] and call the handler
    h(argv[1]);
    // call the lambda, that will parse argv[2] and call the handler
    g(argv[1]);
}

1 个答案:

答案 0 :(得分:2)

使用std::tuplestd::apply

在初始化列表中使用Ts...扩展以按特定顺序构建参数。也许使用对齐的存储或选项来使其变得容易,或者构造和分配,如果懒惰。

可能会将编译时或运行时索引和类型传递给解析代码。

因此派生应该如下:

struct bob : auto_parse< bob, void(int, std::string, double) >{
  int parse( std::string_view& data, size_t arg_index, tag_t<int> ) { return {}; } // actually parse
  // optional!  If missing default parser used.
  // etc
  void execute( int x, std::string const& s, double d ) const { /* code */ }
};

我会避免模棱两可的operator()位。