下面的代码“似乎”有效 - 但是,我有点担心我在标记点处于未指定行为的领域。如果是的话,有人可以给我扔一块骨头,这样我就可以确保在更改编译器时不会突然损坏它吗?
意图(如果不清楚)是我想生成一个能够包装另一个的 std::function - 但以稍微不同的方式处理参数。
/// Some collection of arguments generated at runtime.
class ArgCollection
{
int argCount;
std::variant *arguments;
}
/// generate the wrapping fn
template<class ...Args>
std::function<void(ArgCollection)> GetConvert(std::function<void(Args...)> thing)
{
constexpr std::size_t argCount = sizeof...(Args);
return [argCount, method](const ArgCollection& args) -> void {
if (args.numArguments != argCount)
throw std::invalid_argument("Invalid number of arguments");
int count = 0; <------------ I fear about the usage of this variable.
auto convertedArgs = std::make_tuple(ConvertArg<Args>(args, count++)...);
std::apply(method, convertedArgs);
};
}
/// helper const & reference stripping
template<typename T>
using base_type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
/// Get the idx'th argument, and convert it to what we can hand to the function
template<class T>
static base_type<T> ConvertArg(const ArgCollection &args, int idx)
{
return base_type<T>(args[idx]);
}
最佳答案
auto convertedArgs = std::make_tuple(ConvertArg<Args>(args, count++)...);
这些增量相对于彼此是不确定的。编译器可以自由地以任何顺序执行它们,并且可以因为蝴蝶扇动它的翅膀而改变顺序。 (在 c++17 之前的保证比这更糟糕)在 c++20有一个简单的解决方法:
constexpr std::size_t argCount = sizeof...(Args);
return [&]<std::size_t...Is>(std::index_sequence<Is...>){
return [argCount, method](const ArgCollection& args) -> void {
if (args.numArguments != argCount)
throw std::invalid_argument("Invalid number of arguments");
auto convertedArgs = std::make_tuple(ConvertArg<Args>(args, Is)...);
std::apply(method, convertedArgs);
};
}( std::make_index_sequence<sizeof...(Args)>{} );
我们在其中创建一个索引序列对象并将其解包到函数内的 lambda 中。在 c++17您基本上需要构建和解压缩索引的辅助函数。
template<auto x>
using constant_t = std::integral_constant<std::decay_t<decltype(x)>, x>;
template<auto x>
constexpr constant_t<x> constant_v={};
template<std::size_t...Is, class F>
decltype(auto) index_over( std::index_sequence<Is...>, F&& f ) {
return f( constant_v<Is>... );
}
template<std::size_t N, class F>
decltype(auto) index_upto(F&& f) {
return index_over( std::make_index_sequence<N>{}, std::forward<F>(f) );
}
那么你的代码变成:constexpr std::size_t argCount = sizeof...(Args);
return index_upto<argCount>([&](auto...Is){
return [argCount, method, Is...](const ArgCollection& args) -> void {
if (args.numArguments != argCount)
throw std::invalid_argument("Invalid number of arguments");
auto convertedArgs = std::make_tuple(ConvertArg<Args>(args, Is)...);
std::apply(method, convertedArgs);
};
});
或诸如此类。您还可以编写一个更传统的辅助函数,将索引序列传递给它。
最后,您可以依赖
{}
的事实。基于初始化是有序的。template<class ...Args>
std::function<void(ArgCollection)> GetConvert(std::function<void(Args...)> thing)
{
constexpr std::size_t argCount = sizeof...(Args);
return [argCount, method](const ArgCollection& args) -> void {
if (args.numArguments != argCount)
throw std::invalid_argument("Invalid number of arguments");
int count = 0; <------------ I fear about the usage of this variable.
auto convertedArgs = std::tuple{ConvertArg<Args>(args, count++)...};
std::apply(method, convertedArgs);
};
}
这可能更容易。
关于c++ - 使用带索引的包扩展 - 是 UB 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68882421/