2016-10-09 2 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]); 
} 

,이 컴파일되지 않습니다 : 그것은 다음과 같습니다. 나는 그것을 이해하지만, 나는 아직 대안 구현을 생각해 내지 못했다.

물론이 기능을 잃지 않고 예제에서 전환 가능성에 대한 체크 표시를 생략 할 수 있지만 컴파일러 메시지의 명확성은 약간 있습니다. 내 실제 코드에는 Derived::operator()()의 서명을 기반으로 선택해야하는 operator()()의 다양한 오버로드가 있습니다. 따라서 나는 그러한 검사가 필요할 것입니다.

다른 접근 방법이 있습니까? 제 목표는 가능한 한 단순한 Derived 같은 호출 가능하도록하는 것입니다. 우리는 앞으로 다른 서명을 통해 많은 것을 갖게 될 것입니다. 그 이유가 정확히 무엇입니까 Derived::operator()() 안에 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::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() 비트를 피할 것.

+0

답변 해 주셔서 감사합니다. 나는 당신이'std :: tuple'과'std :: apply'를 사용하여 의미했던 것을 확신하지 못했지만, 당신은 새로운 트랙에 나를 놓았습니다, 그것은 지금 유망 해 보입니다. 문제가 해결되면 검색 대상자 모두에게 해답을 게시합니다. BTW : 구성 요소는 고전적인 객체에만 국한되어서는 안되기 때문에'operator()'를 사용하기로했습니다. 나는 또한 람다 (lambdas) 나 자유 함수를 컴포넌트로 사용할 수 있기를 원한다. 그것이 효과가 있다면, 새로운 아이디어는 심지어 파라미터 파싱을 다음과 같이 활성화 할 수도 있습니다 :-) – cdonat

+0

@cdonat는 a가 아니어야합니다. 실행시 호출을'()'에 래핑하십시오. ['std :: apply'] (http://en.cppreference.com/w/cpp/utility/apply)는 호출 가능한 호출과 튜플을 취하고 튜플을 압축하여 호출합니다. args를 여러 멤버 변수 대신 튜플로 구문 분석하십시오. – Yakk

+0

아, 알겠습니다. 분명히 내 예가 너무 단순했다.실제 코드에서 멤버 변수는 실제 매개 변수 값이 아니라 파서에 대한 지침을 저장합니다. 매개 변수 값은 접두사를 구문 분석하여 파서가 해당 명령을 해석하는 동안 결정됩니다. 그것과 같이 생각하십시오. 자동 d = 유도 된 {std : .regexp { "[0-9] *"s}, std :: regexp { "[^ 0-9.] *"s}, std :: regex { "[0-9 . * "s}}'이후에'd ("123 asdf 5.7 ")'. 아이디어는'(123, "asdf"s, 5.7)'로 호출 연산자를 호출하는 것입니다. – cdonat

관련 문제