가변 템플릿 함수의 이기종 인수 팩에 대한 일반 계산을 수행하는 방법은 무엇입니까?
전제:
가변적 인 템플릿을 조금 가지고 놀다가, 저는 사소한 메타 프로그래밍 작업을 약간 넘어서는 것을 달성하는 것이 곧 상당히 번거로워진다는 것을 깨달았습니다. 특히, iterate , split , loop in a- like fashion 등과 같은 인수 팩에 대한 일반 작업 을 수행하는 방법을 원 했습니다.std::for_each
보고 후 안드레이 Alexandrescu에 의해이 강의 의 바람직에 C에서 ++ 및 2012 년 이후 static if
C로 ++ (으로부터 빌린 구조 D 프로그래밍 언어 ) 나는 어떤 종류의 그 느낌을 가지고 static for
뿐만 아니라 편리 올 것이다 - 그리고 더 많은 이들의 느낌을 static
구조 할 수 이익을 가져 오십시오.
그래서 가변 템플릿 함수 ( pseudo-code ) 의 인수 팩에 대해 이와 같은 결과 를 얻을 수있는 방법이 있는지 궁금해하기 시작했습니다 .
template<typename... Ts>
void my_function(Ts&&... args)
{
static for (int i = 0; i < sizeof...(args); i++) // PSEUDO-CODE!
{
foo(nth_value_of<i>(args));
}
}
컴파일 타임 에 다음과 같이 번역 됩니다 .
template<typename... Ts>
void my_function(Ts&&... args)
{
foo(nth_value_of<0>(args));
foo(nth_value_of<1>(args));
// ...
foo(nth_value_of<sizeof...(args) - 1>(args));
}
원칙적 static_for
으로 훨씬 더 정교한 처리가 가능합니다.
template<typename... Ts>
void foo(Ts&&... args)
{
constexpr s = sizeof...(args);
static for (int i = 0; i < s / 2; i++)
{
// Do something
foo(nth_value_of<i>(args));
}
static for (int i = s / 2; i < s; i++)
{
// Do something different
bar(nth_value_of<i>(args));
}
}
또는 다음과 같은 더 표현적인 관용구 :
template<typename... Ts>
void foo(Ts&&... args)
{
static for_each (auto&& x : args)
{
foo(x);
}
}
관련된 일:
웹에서 몇 가지 검색을 했는데 실제로 존재 하는 것이 있다는 것을 알았습니다 .
- 이 링크 는 매개 변수 팩을 Boost.MPL 벡터로 변환하는 방법을 설명하지만 목표를 향한 절반 (적지 않은 경우)에 불과합니다.
- SO에 대한이 질문 은 유사하고 약간 관련된 메타 프로그래밍 기능 (인수 팩을 두 개로 분할)을 요구하는 것 같습니다. 실제로이 문제와 관련된 것처럼 보이는 SO에 대한 몇 가지 질문이 있지만 그에 대한 답변은 없습니다. 읽음이 만족스럽게 IMHO를 해결합니다.
- Boost.Fusion 은 인수 팩을 튜플 로 변환하는 알고리즘을 정의 하지만 다음을 선호합니다.
- 일부 일반 알고리즘에 완벽하게 전달할 수있는 (그리고 그래야만하는) 인수를 보유 하기 위해 불필요한 임시 를 생성 하지 않습니다 .
- 이를 위해 작은 자체 포함 라이브러리가있는 반면 Boost.Fusion은이 문제를 해결하는 데 필요한 것보다 더 많은 것을 포함 할 가능성이 높습니다.
질문:
기존 접근 방식의 제한없이 원하는 것을 달성하기 위해 템플릿 메타 프로그래밍을 통해 비교적 간단한 방법이 있습니까?
내가 찾은 것에 만족하지 않았기 때문에, 스스로 해결책을 찾으려고 노력했고 결국 인수 팩에 대한 일반 연산을 공식화 할 수 있는 작은 라이브러리 를 작성했습니다 . 내 솔루션에는 다음과 같은 기능이 있습니다.
- 팩 의 인덱스를 계산 하여 지정된 인수 팩의 전체 또는 일부 요소를 반복 할 수 있습니다 .
- 인수 팩의 계산 된 부분을 가변 펑터로 전달할 수 있습니다.
- 비교적 짧은 헤더 파일 하나만 포함하면됩니다.
- 완벽한 포워딩을 광범위하게 사용하여 무거운 인라인을 허용하고 불필요한 복사 / 이동을 방지하여 성능 손실을 최소화합니다.
- 반복 알고리즘의 내부 구현은 메모리 소비를 최소화하기 위해 Empty Base Class Optimization에 의존합니다.
- (비교적으로 템플릿 메타 프로그래밍을 고려할 때) 확장하고 적응하는 것은 쉽습니다.
먼저 라이브러리 로 수행 할 수있는 작업 을 보여준 다음 구현 을 게시합니다 .
사용 사례
다음은 for_each_in_arg_pack()
함수를 사용하여 팩의 모든 인수를 반복하고 입력의 각 인수를 일부 클라이언트 제공 펑터에 전달 하는 방법에 대한 예입니다 (물론 인수 팩에 값이 포함 된 경우 펑터에는 일반 호출 연산자가 있어야합니다. 이기종 유형) :
// Simple functor with a generic call operator that prints its input. This is used by the
// following functors and by some demonstrative test cases in the main() routine.
struct print
{
template<typename T>
void operator () (T&& t)
{
cout << t << endl;
}
};
// This shows how a for_each_*** helper can be used inside a variadic template function
template<typename... Ts>
void print_all(Ts&&... args)
{
for_each_in_arg_pack(print(), forward<Ts>(args)...);
}
print
위 의 펑 터는 더 복잡한 계산에도 사용할 수 있습니다. 특히, 다음은 팩에있는 인수 의 하위 집합 (이 경우 하위 범위 ) 에서 반복하는 방법입니다 .
// Shows how to select portions of an argument pack and
// invoke a functor for each of the selected elements
template<typename... Ts>
void split_and_print(Ts&&... args)
{
constexpr size_t packSize = sizeof...(args);
constexpr size_t halfSize = packSize / 2;
cout << "Printing first half:" << endl;
for_each_in_arg_pack_subset(
print(), // The functor to invoke for each element
index_range<0, halfSize>(), // The indices to select
forward<Ts>(args)... // The argument pack
);
cout << "Printing second half:" << endl;
for_each_in_arg_pack_subset(
print(), // The functor to invoke for each element
index_range<halfSize, packSize>(), // The indices to select
forward<Ts>(args)... // The argument pack
);
}
때로는 요소를 반복하는 대신 인수 팩의 일부를 다른 가변 펑터로 전달하고 각 요소 를 비 가변 펑터에 개별적 으로 전달 하기를 원할 수 있습니다 . 이것은 무엇 알고리즘을 수행 할 수 있습니다 :forward_subpack()
// Functor with variadic call operator that shows the usage of for_each_***
// to print all the arguments of a heterogeneous pack
struct my_func
{
template<typename... Ts>
void operator ()(Ts&&... args)
{
print_all(forward<Ts>(args)...);
}
};
// Shows how to forward only a portion of an argument pack
// to another variadic functor
template<typename... Ts>
void split_and_print(Ts&&... args)
{
constexpr size_t packSize = sizeof...(args);
constexpr size_t halfSize = packSize / 2;
cout << "Printing first half:" << endl;
forward_subpack(my_func(), index_range<0, halfSize>(), forward<Ts>(args)...);
cout << "Printing second half:" << endl;
forward_subpack(my_func(), index_range<halfSize, packSize>(), forward<Ts>(args)...);
}
보다 구체적인 작업의 경우, 인덱싱 하여 팩의 특정 인수를 검색 할 수 있습니다. 이 nth_value_of()
기능이 도우미 first_value_of()
및 last_value_of()
다음 과 함께 수행 할 수있는 작업입니다 .
// Shows that arguments in a pack can be indexed
template<unsigned I, typename... Ts>
void print_first_last_and_indexed(Ts&&... args)
{
cout << "First argument: " << first_value_of(forward<Ts>(args)...) << endl;
cout << "Last argument: " << last_value_of(forward<Ts>(args)...) << endl;
cout << "Argument #" << I << ": " << nth_value_of<I>(forward<Ts>(args)...) << endl;
}
반면에 인수 묶음이 동종인 경우 (즉, 모든 인수가 동일한 유형을 가짐) 다음과 같은 공식이 바람직 할 수 있습니다. is_homogeneous_pack<>
메타 함수는 매개 변수 팩의 모든 종류의 균질 여부를 판단 할 수 있습니다, 주로에 사용하기위한 것입니다 static_assert()
문 :
// Shows the use of range-based for loops to iterate over a
// homogeneous argument pack
template<typename... Ts>
void print_all(Ts&&... args)
{
static_assert(
is_homogeneous_pack<Ts...>::value,
"Template parameter pack not homogeneous!"
);
for (auto&& x : { args... })
{
// Do something with x...
}
cout << endl;
}
마지막으로, 람다 는 펑터의 문법적 설탕 이기 때문에 위의 알고리즘과 함께 사용할 수도 있습니다. 그러나 일반 람다 가 C ++에서 지원 될 때까지는 동종 인수 팩에 대해서만 가능합니다 . 다음 예제는 homogeneous-type<>
동종 팩의 모든 인수 유형을 반환하는 메타 함수 의 사용법도 보여줍니다 .
// ...
static_assert(
is_homogeneous_pack<Ts...>::value,
"Template parameter pack not homogeneous!"
);
using type = homogeneous_type<Ts...>::type;
for_each_in_arg_pack([] (type const& x) { cout << x << endl; }, forward<Ts>(args)...);
이것은 기본적으로 라이브러리가 할 수있는 일이지만 더 복잡한 작업을 수행 하도록 확장 될 수도 있다고 생각 합니다.
이행
이제 구현이 나왔는데 그 자체로는 약간 까다롭기 때문에 주석에 의존하여 코드를 설명하고이 게시물을 너무 길게 만드는 것을 피할 것입니다 (아마도 이미 그렇습니다).
#include <type_traits>
#include <utility>
//===============================================================================
// META-FUNCTIONS FOR EXTRACTING THE n-th TYPE OF A PARAMETER PACK
// Declare primary template
template<int I, typename... Ts>
struct nth_type_of
{
};
// Base step
template<typename T, typename... Ts>
struct nth_type_of<0, T, Ts...>
{
using type = T;
};
// Induction step
template<int I, typename T, typename... Ts>
struct nth_type_of<I, T, Ts...>
{
using type = typename nth_type_of<I - 1, Ts...>::type;
};
// Helper meta-function for retrieving the first type in a parameter pack
template<typename... Ts>
struct first_type_of
{
using type = typename nth_type_of<0, Ts...>::type;
};
// Helper meta-function for retrieving the last type in a parameter pack
template<typename... Ts>
struct last_type_of
{
using type = typename nth_type_of<sizeof...(Ts) - 1, Ts...>::type;
};
//===============================================================================
// FUNCTIONS FOR EXTRACTING THE n-th VALUE OF AN ARGUMENT PACK
// Base step
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
typename std::enable_if<(I == 0), decltype(std::forward<T>(t))>::type
{
return std::forward<T>(t);
}
// Induction step
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
typename std::enable_if<(I > 0), decltype(
std::forward<typename nth_type_of<I, T, Ts...>::type>(
std::declval<typename nth_type_of<I, T, Ts...>::type>()
)
)>::type
{
using return_type = typename nth_type_of<I, T, Ts...>::type;
return std::forward<return_type>(nth_value_of<I - 1>((std::forward<Ts>(args))...));
}
// Helper function for retrieving the first value of an argument pack
template<typename... Ts>
auto first_value_of(Ts&&... args) ->
decltype(
std::forward<typename first_type_of<Ts...>::type>(
std::declval<typename first_type_of<Ts...>::type>()
)
)
{
using return_type = typename first_type_of<Ts...>::type;
return std::forward<return_type>(nth_value_of<0>((std::forward<Ts>(args))...));
}
// Helper function for retrieving the last value of an argument pack
template<typename... Ts>
auto last_value_of(Ts&&... args) ->
decltype(
std::forward<typename last_type_of<Ts...>::type>(
std::declval<typename last_type_of<Ts...>::type>()
)
)
{
using return_type = typename last_type_of<Ts...>::type;
return std::forward<return_type>(nth_value_of<sizeof...(Ts) - 1>((std::forward<Ts>(args))...));
}
//===============================================================================
// METAFUNCTION FOR COMPUTING THE UNDERLYING TYPE OF HOMOGENEOUS PARAMETER PACKS
// Used as the underlying type of non-homogeneous parameter packs
struct null_type
{
};
// Declare primary template
template<typename... Ts>
struct homogeneous_type;
// Base step
template<typename T>
struct homogeneous_type<T>
{
using type = T;
static const bool isHomogeneous = true;
};
// Induction step
template<typename T, typename... Ts>
struct homogeneous_type<T, Ts...>
{
// The underlying type of the tail of the parameter pack
using type_of_remaining_parameters = typename homogeneous_type<Ts...>::type;
// True if each parameter in the pack has the same type
static const bool isHomogeneous = std::is_same<T, type_of_remaining_parameters>::value;
// If isHomogeneous is "false", the underlying type is the fictitious null_type
using type = typename std::conditional<isHomogeneous, T, null_type>::type;
};
// Meta-function to determine if a parameter pack is homogeneous
template<typename... Ts>
struct is_homogeneous_pack
{
static const bool value = homogeneous_type<Ts...>::isHomogeneous;
};
//===============================================================================
// META-FUNCTIONS FOR CREATING INDEX LISTS
// The structure that encapsulates index lists
template <unsigned... Is>
struct index_list
{
};
// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
// Declare primary template for index range builder
template <unsigned MIN, unsigned N, unsigned... Is>
struct range_builder;
// Base step
template <unsigned MIN, unsigned... Is>
struct range_builder<MIN, MIN, Is...>
{
typedef index_list<Is...> type;
};
// Induction step
template <unsigned MIN, unsigned N, unsigned... Is>
struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
{
};
}
// Meta-function that returns a [MIN, MAX) index range
template<unsigned MIN, unsigned MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
//===============================================================================
// CLASSES AND FUNCTIONS FOR REALIZING LOOPS ON ARGUMENT PACKS
// Implementation inspired by @jogojapan's answer to this question:
// http://stackoverflow.com/questions/14089637/return-several-arguments-for-another-function-by-a-single-function
// Collects internal details for implementing functor invocation
namespace detail
{
// Functor invocation is realized through variadic inheritance.
// The constructor of each base class invokes an input functor.
// An functor invoker for an argument pack has one base class
// for each argument in the pack
// Realizes the invocation of the functor for one parameter
template<unsigned I, typename T>
struct invoker_base
{
template<typename F, typename U>
invoker_base(F&& f, U&& u) { f(u); }
};
// Necessary because a class cannot inherit the same class twice
template<unsigned I, typename T>
struct indexed_type
{
static const unsigned int index = I;
using type = T;
};
// The functor invoker: inherits from a list of base classes.
// The constructor of each of these classes invokes the input
// functor with one of the arguments in the pack.
template<typename... Ts>
struct invoker : public invoker_base<Ts::index, typename Ts::type>...
{
template<typename F, typename... Us>
invoker(F&& f, Us&&... args)
:
invoker_base<Ts::index, typename Ts::type>(std::forward<F>(f), std::forward<Us>(args))...
{
}
};
}
// The functor provided in the first argument is invoked for each
// argument in the pack whose index is contained in the index list
// specified in the second argument
template<typename F, unsigned... Is, typename... Ts>
void for_each_in_arg_pack_subset(F&& f, index_list<Is...> const& i, Ts&&... args)
{
// Constructors of invoker's sub-objects will invoke the functor.
// Note that argument types must be paired with numbers because the
// implementation is based on inheritance, and one class cannot
// inherit the same base class twice.
detail::invoker<detail::indexed_type<Is, typename nth_type_of<Is, Ts...>::type>...> invoker(
f,
(nth_value_of<Is>(std::forward<Ts>(args)...))...
);
}
// The functor provided in the first argument is invoked for each
// argument in the pack
template<typename F, typename... Ts>
void for_each_in_arg_pack(F&& f, Ts&&... args)
{
for_each_in_arg_pack_subset(f, index_range<0, sizeof...(Ts)>(), std::forward<Ts>(args)...);
}
// The functor provided in the first argument is given in input the
// arguments in whose index is contained in the index list specified
// as the second argument.
template<typename F, unsigned... Is, typename... Ts>
void forward_subpack(F&& f, index_list<Is...> const& i, Ts&&... args)
{
f((nth_value_of<Is>(std::forward<Ts>(args)...))...);
}
// The functor provided in the first argument is given in input all the
// arguments in the pack.
template<typename F, typename... Ts>
void forward_pack(F&& f, Ts&&... args)
{
f(std::forward<Ts>(args)...);
}
결론
물론이 질문에 대한 답을 직접 제공했지만 (실제로이 사실 때문에 ) "관련 작업"섹션에서 언급 한 것 외에 내가 놓친 대안 또는 더 나은 솔루션이 있는지 듣고 싶습니다. 질문의.
토론을 기반으로이 코드를 게시하겠습니다.
#include <initializer_list>
#define EXPAND(EXPR) std::initializer_list<int>{((EXPR),0)...}
// Example of use:
#include <iostream>
#include <utility>
void print(int i){std::cout << "int: " << i << '\n';}
int print(double d){std::cout << "double: " << d << '\n';return 2;}
template<class...T> void f(T&&...args){
EXPAND(print(std::forward<T>(args)));
}
int main(){
f();
f(1,2.,3);
}
나는에 생성 된 코드를 확인 g++ -std=c++11 -O1
하고 main
만 3 호출을 포함 print
, 확장 헬퍼의 흔적이 없다.
사용하여 열거 (파이썬 ALA) 솔루션을.
용법:
void fun(int i, size_t index, size_t size) {
if (index != 0) {
std::cout << ", ";
}
std::cout << i;
if (index == size - 1) {
std::cout << "\n";
}
} // fun
enumerate(fun, 2, 3, 4);
// Expected output: "2, 3, 4\n"
// check it at: http://liveworkspace.org/code/1cydbw$4
암호:
// Fun: expects a callable of 3 parameters: Arg, size_t, size_t
// Arg: forwarded argument
// size_t: index of current argument
// size_t: number of arguments
template <typename Fun, typename... Args, size_t... Is>
void enumerate_impl(Fun&& fun, index_list<Is...>, Args&&... args) {
std::initializer_list<int> _{
(fun(std::forward<Args>(args), Is, sizeof...(Is)), 0)...
};
(void)_; // placate compiler, only the side-effects interest us
}
template <typename Fun, typename... Args>
void enumerate(Fun&& fun, Args&&... args) {
enumerate_impl(fun,
index_range<0, sizeof...(args)>(),
std::forward<Args>(args)...);
}
범위 빌더 (솔루션에서 유추 됨) :
// The structure that encapsulates index lists
template <size_t... Is>
struct index_list
{
};
// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
// Declare primary template for index range builder
template <size_t MIN, size_t N, size_t... Is>
struct range_builder;
// Base step
template <size_t MIN, size_t... Is>
struct range_builder<MIN, MIN, Is...>
{
typedef index_list<Is...> type;
};
// Induction step
template <size_t MIN, size_t N, size_t... Is>
struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
{
};
}
// Meta-function that returns a [MIN, MAX) index range
template<size_t MIN, size_t MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
... 표기법에는 다음과 같은 몇 가지 흥미로운 옵션이 있습니다.
template<typename T>
int print(const T& x) {
std::cout << "<" << x << ">";
return 0;
}
void pass(...) {}
template<typename... TS>
void printall(TS... ts){
pass(print(ts)...);
}
불행히도, 나는 인쇄 함수가 호출되는 순서를 강제하는 방법을 모른다 (내 컴파일러에서 역으로). print는 무언가를 반환해야합니다.
이 트릭은 주문에 신경 쓰지 않는 경우 유용 할 수 있습니다.
몇 가지 다른 게시물을 읽고 잠시 땜질을 한 후 다음을 생각해 냈습니다 (위와 약간 비슷하지만 구현이 약간 다릅니다). Visual Studio 2013 컴파일러를 사용하여 작성했습니다.
람다 식을 사용한 사용법-
static_for_each()(
[](std::string const& str)
{
std::cout << str << std::endl;
}, "Hello, ", "Lambda!");
람다를 사용할 때의 단점은 매개 변수가 람다의 매개 변수 목록에 선언 된 동일한 유형이어야한다는 것입니다. 즉, 한 가지 유형에서만 작동합니다. 템플릿 함수를 사용하려면 다음 예제를 사용할 수 있습니다.
구조체 래퍼 펑터를 사용한 사용법-
struct print_wrapper
{
template <typename T>
void operator()(T&& str)
{
std::cout << str << " ";
}
};
//
// A little test object we can use.
struct test_object
{
test_object() : str("I'm a test object!") {}
std::string str;
};
std::ostream& operator<<(std::ostream& os, test_object t)
{
os << t.str;
return os;
}
//
// prints: "Hello, Functor! 1 2 I'm a test object!"
static_for_each()(print_wrapper(), "Hello,", "Functor!", 1, 2.0f, test_object());
이를 통해 원하는 유형을 전달하고 functor를 사용하여 작업 할 수 있습니다. 나는 이것이 꽤 깨끗하다는 것을 알았고 내가 원하는 것을 잘 작동합니다. 다음과 같은 함수 매개 변수 팩과 함께 사용할 수도 있습니다.
template <typename T, typename... Args>
void call(T f, Args... args)
{
static_for_each()(f, args...);
}
call(print_wrapper(), "Hello", "Call", "Wrapper!");
다음은 구현입니다.
//
// Statically iterate over a parameter pack
// and call a functor passing each argument.
struct static_for_each
{
private:
//
// Get the parameter pack argument at index i.
template <size_t i, typename... Args>
static auto get_arg(Args&&... as)
-> decltype(std::get<i>(std::forward_as_tuple(std::forward<Args>(as)...)))
{
return std::get<i>(std::forward_as_tuple(std::forward<Args>(as)...));
}
//
// Recursive template for iterating over
// parameter pack and calling the functor.
template <size_t Start, size_t End>
struct internal_static_for
{
template <typename Functor, typename... Ts>
void operator()(Functor f, Ts&&... args)
{
f(get_arg<Start>(args...));
internal_static_for<Start + 1, End>()(f, args...);
}
};
//
// Specialize the template to end the recursion.
template <size_t End>
struct internal_static_for<End, End>
{
template <typename Functor, typename... Ts>
void operator()(Functor f, Ts&&... args){}
};
public:
//
// Publically exposed operator()().
// Handles template recursion over parameter pack.
// Takes the functor to be executed and a parameter
// pack of arguments to pass to the functor, one at a time.
template<typename Functor, typename... Ts>
void operator()(Functor f, Ts&&... args)
{
//
// Statically iterate over parameter
// pack from the first argument to the
// last, calling functor f with each
// argument in the parameter pack.
internal_static_for<0u, sizeof...(Ts)>()(f, args...);
}
};
사람들이 유용하다고 생각하기를 바랍니다 :-)
'program tip' 카테고리의 다른 글
RESTful 웹 서비스에서 로그인을 어떻게 구현합니까? (0) | 2020.10.15 |
---|---|
목록 또는지도의 일부를 공유하기위한 YAML 구문이 있습니까? (0) | 2020.10.15 |
이 모든 * .FileListAbsolute.txt 파일은 무엇입니까? (0) | 2020.10.15 |
라즈베리 파이의 모노 (0) | 2020.10.15 |
가변 템플릿 인수를 저장하는 방법은 무엇입니까? (0) | 2020.10.15 |