2016-10-09 16 views
1

私の目標は、パラメータのリストを持つ呼び出し可能(例ではDerived)を作成することです。単一のパラメータで呼び出され、パラメータリストの値を解析するために使用されます。パラメータを呼び出すと、単一のパラメータから解析されます

私の現在の試みは、一種のバインディングメカニズムと構造的に似ています。 typename parameter_type<Derived, 0>::type>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]); 
} 

:それはこのようになります。私はそれを理解していますが、まだ別の実装を思いついたわけではありません。

この例では、機能を失うことなく、コンパチビリティのチェックを省略することができますが、コンパイラのメッセージには明快さがあります。実際のコードではoperator()()のさまざまなオーバーロードがあり、それはDerived::operator()()の署名に基づいて選択する必要があります。したがって、私はそのようなチェックが必要です。

別の方法がありますか?私の目標は、できるだけシンプルなDerivedのような呼び出し可能なものを作ることです。私たちは将来、さまざまな署名でそれらをたくさん持っていきます。それがまさに理由です。私はなぜの中でprefixを解析するのを避けようとしていますか?

この質問の将来の読者の利益のためのソリューション

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

答えて

2

std::tupleおよびstd::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()ビットを避けるだろう。

+0

ご回答いただきありがとうございます。私はあなたが 'std :: tuple'と' std :: apply'を使って何を意味していたのかは分かりませんが、あなたは私を新しいトラックに入れました。それがうまくいくならば、ここで解決策を検索するすべての人の回答として投稿します。 BTW:コンポーネントが古典的なオブジェクトに限定されるべきではないので、私は 'operator()'を使うことを選択しました。また、少なくとも標準的な署名では、lambdaや自由な関数をコンポーネントとして使用できるようにしたいと考えています。それがうまくいくならば、新しいアイデアは、これらのパラメータの解析も可能にするかもしれません: – cdonat

+0

@cdonatはaではないラムダを持っています。実行時に '()'に呼び出しをラップします。 ['std :: apply'](http://en.cppreference.com/w/cpp/utility/apply)は、呼び出し可能とタプルを取り、呼び出しを行うためにタプルを展開します。 argsをメンバ変数の束ではなくタプルにパースします。 – Yakk

+0

ああ、そうです。明らかに私の例はあまりに単純化していた。実際のコードでは、メンバ変数は実際のパラメータ値を格納するのではなく、パーサのための命令を格納します。次に、プレフィックスを解析することによってパラメータ値が決定され、パーサはそれらの命令を解釈する。それを例えばのように考える。 ''^0-9。* * 's}、std :: regex {"[0-9]}"自動d =派生{std:.regexp {"[0-9] *" s}、std :: regexp { 。] * "s}}'とそれ以降は 'd(" 123 asdf 5.7 ")'となります。その考えは、 '(123、" asdf "s、5.7)'を使ってd 's呼び出し演算子を呼び出すことです。 – cdonat

関連する問題