2017-09-22 6 views
3

런타임 조건에 따라 범위를 직접 또는 역순으로 반복하고 싶습니다. 이것은 일반적으로 다음과 같은 코드 결과 다음부스트 범위 어댑터 구현하기 reversed_if

if (reverse) { 
    using boost::adaptors::reversed; 
    for (auto const & x : range | reversed) do_stuff(x); 
} else { 
    for (auto const & x : range) do_stuff(x); 
} 

또는 코드 중복 (제 1 일)을 포함하거나 (두 번째) 비효율적

std::vector<Foo> v(range.begin(), range.end()); 
if (reverse) boost::range::reverse(v); 
for (auto const & x : v) do_stuff(x); 

.

내가

using boost::adaptors::reversed_if; 
for (auto const & x : range | reversed_if(reverse)) do_stuff(x); 

을 쓸 수 있도록 내가 조건부 범위를 반전 것 인 가상 부스트 범위 어댑터에 대해 여러 번 생각을 해 봤는데 내가 (here부터) 그것을 자신을 구현할 수 있지만이 아니다 계속 진행하는 방법에 대해 확신합니다. 런타임 조건을 지원하기 위해 각 반복마다 부울 값을 검사하여 반복 방향을 결정하거나 가상화를 사용하여 반복 코드에 전달해야합니다. 이것이 부스트 범위 어댑터에서 제공되지 않는 이유입니까?

대체 솔루션?

답변

1

각 증가분에서 어떤 방법으로 런타임 검사를 피하려면 런타임 값을 루프 구조 외부의 컴파일 시간 값으로 변환해야합니다.

이 경우에는 반복하지 않을 범위가 달라지기를 원합니다.

쉬운 방법이 방법은 몸체에 람다를 작성한 다음 선택할 루프를 선택하는 것입니다.

auto do_stuff = [&](auto&& elem){ /* code */ }; 
if (reverse) { 
    using boost::adaptors::reversed; 
    for (auto const & x : range | reversed) do_stuff(x); 
} else { 
    for (auto const & x : range) do_stuff(x); 
} 

우리는 어떻게 루프에 대한 정적 유형 정보를 두 개의 서로 다른 루프를 만들고, 루프의 외부 런타임 파견을 수행했다.

우리는 어댑터 다음과 같이 할 수 있습니다 : magic_switch는 첫 번째 인수로 인덱스 (std::size_t)를 취

magic_switch 
    (reverse) 
    (range, range|reversed) 
    (
    [&](auto&& range){ 
     for (auto const& x : decltype(range)(range)) { 
     do_stuff(x); 
     } 
    } 
); 

. 인수의 목록을 취하는 람다를 반환합니다. 이 함수는 람다를 반환하고 람다를 취하여 두 번째 목록에서 첫 번째 인수의 인덱스에 의해 결정된 두 번째 목록의 인수를 전달합니다.

inline auto magic_switch(std::size_t I) { 
    return [I](auto&&...options) { 
    return [I, &](auto&& f)->decltype(auto) { 
     using fptr = void(*)(void const volatile* op, decltype(f)); 
     static const fptr table[] = { 
     +[](void const volatile* op_in, decltype(f) f) { 
      auto* option = static_cast<std::decay_t<decltype(options)>*>(op_in); 
      decltype(f)(f)(decltype(options)(*option)); 
     }... 
     }; 
     const volatile void* ptrs[] = {std::addressof(options)...}; 
     if (I >= sizeof...(options)) I = sizeof...(options)-1; 
     if (I == -1) return; 
     table[I](ptrs[I], decltype(f)(f)); 
    }; 
    }; 
} 

은 구현시 스케치 (빌드 오류 포함)입니다.

어려운 부분은 "유형 흐름"(용어를 동전에 넣기)이 일반적으로 원하는 방식대로 진행되지 않는다는 것입니다. 그래서 저는 기본적으로 연속 통과 스타일을 사용해야합니다.

많은 컴파일러는 전체 람다를 포함하는 팩 확장에 만족하지 않습니다. 함수 포인터를 반환하는 도우미 기능을 쓸 수있다 :

template<class F> 
using f_ptr = void(*)(const volatile void*, F&&); 

template<class Option, class F> 
f_ptr<F> get_f_ptr() { 
    return +[](void const volatile* op_in, F&& f) { 
    auto* option = static_cast<std::decay_t<Option>*>(op_in); 
    std::forward<F>(f)(std::forward<Option>(*option)); 
    }; 
} 

은 다음과 테이블을 대체 :

 static const fptr table[] = { 
     get_fptr<decltype(options), decltype(f)>()... 
     }; 

을하는 컴파일러에.

+0

와우! 고맙습니다! 나는 아이디어를 얻었지만 매직 스위치의 구현 세부 사항을 이해하려고 노력할 것입니다. 'decltype (range) (range)'의 요점은 무엇입니까? 이것은 캐스트입니까, 왜 필요합니까? –

+1

@ChristopheFuzier '자동 &&'참조의 경우 완벽한 전달을 수행합니다. "범위로 선언 된 형식"으로 읽습니다. 그것은'std :: forward (range)'와 동일하지만'range'의 타입이'auto &&'또는'T &&'(forwarding reference) 인 한 절반입니다. 'range '가 값 유형 ('auto' 또는'T')이면 작동하지 않습니다. – Yakk

+0

@ChristopheFuzier 또한 'magic_switch'는 대신 'variant'를 기반으로 작동 할 수 있습니다. – Yakk