[英]c++ generic compile-time for loop
在某些情況下,在編譯時評估/展開for
循環可能是有用/必要的。 例如,要迭代tuple
的元素,需要使用std::get<I>
,這取決於模板int
參數I
,因此必須在編譯時進行評估。 使用遞歸編寫一個能解決一個具體問題,例如討論在這里 , 在這里 ,並專門針對std::tuple
在這里 。
我很感興趣,但是,關於如何實現一個通用的編譯時for
循環。
以下c++17
代碼實現了這個想法
#include <utility>
#include <tuple>
#include <string>
#include <iostream>
template <int start, int end, template <int> class OperatorType, typename... Args>
void compile_time_for(Args... args)
{
if constexpr (start < end)
{
OperatorType<start>()(std::forward<Args>(args)...);
compile_time_for<start + 1, end, OperatorType>(std::forward<Args>(args)...);
}
}
template <int I>
struct print_tuple_i {
template <typename... U>
void operator()(const std::tuple<U...>& x) { std::cout << std::get<I>(x) << " "; }
};
int main()
{
std::tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0, 3, print_tuple_i>(x);
return 0;
}
雖然代碼有效,但是能夠簡單地向例程compile_time_for
提供模板函數,而不是在每次迭代時實例化的模板類會更好。
但是,如下所示的代碼不能在c++17
編譯
#include <utility>
#include <tuple>
#include <string>
#include <iostream>
template <int start, int end, template <int, typename...> class F, typename... Args>
void compile_time_for(F f, Args... args)
{
if constexpr (start < end)
{
f<start>(std::forward<Args>(args)...);
compile_time_for<start + 1, end>(f, std::forward<Args>(args)...);
}
}
template <int I, typename... U>
void myprint(const std::tuple<U...>& x) { std::cout << std::get<I>(x) << " "; }
int main()
{
std::tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0, 3>(myprint, x);
return 0;
}
使用gcc 7.3.0和選項std=c++17
,第一個錯誤是
for2.cpp:7:25: error: ‘auto’ parameter not permitted in this context
void compile_time_for(F f, Args... args)
問題是:
compile_time_for
,以便它接受模板函數作為它的第一個參數? OperatorType<start>
的對象? c++20
引入類似編譯時for循環的功能?
- 有沒有辦法編寫compile_time_for,以便它接受模板函數作為它的第一個參數?
簡答:不。
答案很長:模板函數不是一個對象,是一個對象的集合,你可以傳遞給一個函數,一個參數,一個對象,一個非對象的集合。
這類問題的通常解決方案是將模板函數包裝在類中並傳遞類的對象(或者只是類型,如果函數被包裝為靜態方法)。 這正是您在工作代碼中采用的解決方案。
- 如果問題1是肯定的,那么第一個工作代碼中是否存在開銷,因為例程在每次循環迭代時都會創建一個類型為OperatorType的對象?
問題1是否定的。
- 是否有計划在即將推出的c ++ 20中引入類似編譯時for循環的功能?
我不知道C ++ 20是否足以回答這個問題,但我想不會傳遞一組函數。
無論如何,你可以使用從C ++ 14開始的std::make_index_sequence
/ std::index_sequence
進行一種編譯時循環。
例如,如果您接受在myprint()
函數之外提取touple值,則可以將其包裝在lambda中並按如下方式編寫內容(使用C ++ 17模板折疊;在C ++ 14中稍微復雜一點) )
#include <utility>
#include <tuple>
#include <string>
#include <iostream>
template <typename T>
void myprint (T const & t)
{ std::cout << t << " "; }
template <std::size_t start, std::size_t ... Is, typename F, typename ... Ts>
void ctf_helper (std::index_sequence<Is...>, F f, std::tuple<Ts...> const & t)
{ (f(std::get<start + Is>(t)), ...); }
template <std::size_t start, std::size_t end, typename F, typename ... Ts>
void compile_time_for (F f, std::tuple<Ts...> const & t)
{ ctf_helper<start>(std::make_index_sequence<end-start>{}, f, t); }
int main()
{
std::tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0, 3>([](auto const & v){ myprint(v); }, x);
return 0;
}
如果你真的想要在函數內部提取元組元素(或元組元素),我能想象的最好的是將你的第一個例子變換如下
#include <utility>
#include <tuple>
#include <string>
#include <iostream>
template <std::size_t start, template <std::size_t> class OT,
std::size_t ... Is, typename... Args>
void ctf_helper (std::index_sequence<Is...> const &, Args && ... args)
{ (OT<start+Is>{}(std::forward<Args>(args)...), ...); }
template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename... Args>
void compile_time_for (Args && ... args)
{ ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
std::forward<Args>(args)...); }
template <std::size_t I>
struct print_tuple_i
{
template <typename ... U>
void operator() (std::tuple<U...> const & x)
{ std::cout << std::get<I>(x) << " "; }
};
int main()
{
std::tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0u, 3u, print_tuple_i>(x);
return 0;
}
- 編輯 -
OP問道
使用index_sequence比我的第一個代碼有一些優勢嗎?
我不是專家,但這樣可以避免遞歸。 從模板的角度來看,編譯器具有遞歸限制,可以是嚴格的。 這樣你就可以避免它們。
此外,如果設置模板參數
end > start
,則代碼不會編譯。 (可以想象一種情況,您希望編譯器確定是否實例化了一個循環)
我想你的意思是我的代碼在start > end
不能編譯。
不好的部分是沒有檢查這個問題所以編譯器嘗試編譯我的代碼也是在這種情況下; 所以遇到
std::make_index_sequence<end-start>{}
end - start
是一個負數,但是由期望無符號數的模板使用。 所以end - start
成為一個非常好的正數,這可能會導致問題。
你可以避免在compile_time_for()
強加static_assert()
這個問題
template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename... Args>
void compile_time_for (Args && ... args)
{
static_assert( end >= start, "start is bigger than end");
ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
std::forward<Args>(args)...);
}
或者也許你可以使用SFINAE來禁用該功能
template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename... Args>
std::enable_if_t<(start <= end)> compile_time_for (Args && ... args)
{ ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
std::forward<Args>(args)...); }
如果需要,可以使用SFINAE添加重載的compile_time_for()
版本來管理end < start
case
template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename ... Args>
std::enable_if_t<(start > end)> compile_time_for (Args && ...)
{ /* manage the end < start case in some way */ }
我將回答有關如何修復上一個代碼示例的問題。
它不編譯的原因在於:
template <int start, int end, template <int, typename...> class F, typename... Args>
void compile_time_for(F f, Args... args)
/\
F是一個模板,如果沒有替換模板參數,則不能擁有模板類的對象。 例如,你不能擁有std::vector
類型的對象,但可以擁有std::vector<int>
。 我建議你用模板運算符()制作F
仿函數:
#include <utility>
#include <tuple>
#include <string>
#include <iostream>
template <int start, int end, typename F, typename... Args>
void compile_time_for(F f, Args... args)
{
if constexpr (start < end)
{
f.template operator()<start>(std::forward<Args>(args)...);
compile_time_for<start + 1, end>(f, std::forward<Args>(args)...);
}
}
struct myprint
{
template <int I, typename... U>
void operator()(const std::tuple<U...>& x) { std::cout << std::get<I>(x) << " "; }
};
int main()
{
std::tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0, 3>(myprint(), x);
return 0;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.