c++ - 使用带索引的包扩展 - 是 UB 吗?

标签 c++ c++17 variadic-templates

下面的代码“似乎”有效 - 但是,我有点担心我在标记点处于未指定行为的领域。如果是的话,有人可以给我扔一块骨头,这样我就可以确保在更改编译器时不会突然损坏它吗?
意图(如果不清楚)是我想生成一个能够包装另一个的 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++)...);
这些增量相对于彼此是不确定的。编译器可以自由地以任何顺序执行它们,并且可以因为蝴蝶扇动它的翅膀而改变顺序。 (在 之前的保证比这更糟糕)
有一个简单的解决方法:
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 中。
您基本上需要构建和解压缩索引的辅助函数。
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/

相关文章:

c++ - Sendto() 函数给出错误。如何调试它们?

c++ - Qt在QTableView中嵌入QListView

c++ - 折叠嵌套的 std::future

c++ - 在 CUDA 中合并读/写

c++ - 搜索 vector 中的一系列记录

c++ - vector::emplace_back结果两次调用析构函数

c++ - 从另一个 std::Optional 和其他东西初始化 std::Optional 的惯用方法

amazon-web-services - 如何在速度模板中使用字符串替换(AWS appsync + elasticsearch)?

c++ - 为什么这个嵌套的可变参数模板是无效参数?

c++ - 简洁地重写一组参数数量可变的函数